From bfb71f23c9f2e1c3b9f6525ef1ed8b1185425c6d Mon Sep 17 00:00:00 2001 From: graffy76 Date: Fri, 16 Aug 2013 17:26:23 -0500 Subject: [PATCH 001/434] Changed filter mechanism for game / addons Filters by number of master references, regardless of extension --- apps/opencs/view/doc/filedialog.cpp | 8 ++++---- components/fileorderlist/model/datafilesmodel.cpp | 9 +++++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index f956317a71..02421a7883 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -25,13 +25,13 @@ FileDialog::FileDialog(QWidget *parent) : mDataFilesModel = new DataFilesModel(this); mMastersProxyModel = new QSortFilterProxyModel(); - mMastersProxyModel->setFilterRegExp(QString("^.*\\.esm")); - mMastersProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + mMastersProxyModel->setFilterRegExp("game"); //QString("^.*\\.esm")); + mMastersProxyModel->setFilterRole (Qt::UserRole); mMastersProxyModel->setSourceModel(mDataFilesModel); mPluginsProxyModel = new PluginsProxyModel(); - mPluginsProxyModel->setFilterRegExp(QString("^.*\\.esp")); - mPluginsProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + mPluginsProxyModel->setFilterRegExp("addon"); //QString("^.*\\.esp")); + mPluginsProxyModel->setFilterRole (Qt::UserRole); mPluginsProxyModel->setSourceModel(mDataFilesModel); mFilterProxyModel = new QSortFilterProxyModel(); diff --git a/components/fileorderlist/model/datafilesmodel.cpp b/components/fileorderlist/model/datafilesmodel.cpp index 02a6766b02..cf1fa1b0a5 100644 --- a/components/fileorderlist/model/datafilesmodel.cpp +++ b/components/fileorderlist/model/datafilesmodel.cpp @@ -144,6 +144,15 @@ QVariant DataFilesModel::data(const QModelIndex &index, int role) const return tooltip; } + + case Qt::UserRole: + { + if (file->masters().size() == 0) + return "game"; + else + return "addon"; + } + default: return QVariant(); } From 84e5c2610ab0347127645e277f08dd83e11383fd Mon Sep 17 00:00:00 2001 From: graffy76 Date: Fri, 16 Aug 2013 18:00:23 -0500 Subject: [PATCH 002/434] Implemented combobox for game file selection --- apps/launcher/datafilespage.cpp | 2 +- apps/opencs/view/doc/filedialog.cpp | 20 +++++++------- apps/opencs/view/doc/filedialog.hpp | 2 +- .../fileorderlist/utils/profilescombobox.cpp | 16 +++++++++++ .../fileorderlist/utils/profilescombobox.hpp | 5 +++- files/ui/datafilespage.ui | 27 +++---------------- 6 files changed, 36 insertions(+), 36 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index add3dea40e..1fafd59226 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -101,7 +101,7 @@ DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gam connect(mDataFilesModel, SIGNAL(layoutChanged()), this, SLOT(updateViews())); - connect(filterLineEdit, SIGNAL(textChanged(QString)), this, SLOT(filterChanged(QString))); + //connect(filterLineEdit, SIGNAL(textChanged(QString)), this, SLOT(filterChanged(QString))); connect(splitter, SIGNAL(splitterMoved(int,int)), this, SLOT(updateSplitter())); diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index 02421a7883..b06c970085 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -41,6 +41,8 @@ FileDialog::FileDialog(QWidget *parent) : QCheckBox checkBox; unsigned int height = checkBox.sizeHint().height() + 4; + masterView->setModel(mMastersProxyModel); + mastersTable->setModel(mMastersProxyModel); mastersTable->setObjectName("MastersTable"); mastersTable->setContextMenuPolicy(Qt::CustomContextMenu); @@ -83,12 +85,12 @@ FileDialog::FileDialog(QWidget *parent) : mNameLabel = new QLabel(tr("File Name:"), this); QRegExpValidator *validator = new QRegExpValidator(QRegExp("^[a-zA-Z0-9\\s]*$")); - mNameLineEdit = new LineEdit(this); - mNameLineEdit->setValidator(validator); + //mNameLineEdit = new LineEdit(this); + //mNameLineEdit->setValidator(validator); nameLayout->addSpacerItem(spacer); nameLayout->addWidget(mNameLabel); - nameLayout->addWidget(mNameLineEdit); + //nameLayout->addWidget(mNameLineEdit); mButtonBox = new QDialogButtonBox(this); @@ -109,9 +111,9 @@ FileDialog::FileDialog(QWidget *parent) : connect(mDataFilesModel, SIGNAL(layoutChanged()), this, SLOT(updateViews())); connect(mDataFilesModel, SIGNAL(checkedItemsChanged(QStringList)), this, SLOT(updateOpenButton(QStringList))); - connect(mNameLineEdit, SIGNAL(textChanged(QString)), this, SLOT(updateCreateButton(QString))); + //connect(mNameLineEdit, SIGNAL(textChanged(QString)), this, SLOT(updateCreateButton(QString))); - connect(filterLineEdit, SIGNAL(textChanged(QString)), this, SLOT(filterChanged(QString))); + //connect(filterLineEdit, SIGNAL(textChanged(QString)), this, SLOT(filterChanged(QString))); connect(pluginsTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(setCheckState(QModelIndex))); connect(mastersTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(setCheckState(QModelIndex))); @@ -223,7 +225,7 @@ QStringList FileDialog::checkedItemsPaths() QString FileDialog::fileName() { - return mNameLineEdit->text(); + //return mNameLineEdit->text(); } void FileDialog::openFile() @@ -231,7 +233,7 @@ void FileDialog::openFile() setWindowTitle(tr("Open")); mNameLabel->hide(); - mNameLineEdit->hide(); + //mNameLineEdit->hide(); mCreateButton->hide(); mButtonBox->removeButton(mCreateButton); @@ -249,8 +251,8 @@ void FileDialog::newFile() setWindowTitle(tr("New")); mNameLabel->show(); - mNameLineEdit->clear(); - mNameLineEdit->show(); + //mNameLineEdit->clear(); + //mNameLineEdit->show(); mCreateButton->show(); mButtonBox->setStandardButtons(QDialogButtonBox::Cancel); diff --git a/apps/opencs/view/doc/filedialog.hpp b/apps/opencs/view/doc/filedialog.hpp index b21618d5de..4c3fe9ffab 100644 --- a/apps/opencs/view/doc/filedialog.hpp +++ b/apps/opencs/view/doc/filedialog.hpp @@ -51,7 +51,7 @@ private slots: private: QLabel *mNameLabel; - LineEdit *mNameLineEdit; + //LineEdit *mNameLineEdit; QPushButton *mCreateButton; QDialogButtonBox *mButtonBox; diff --git a/components/fileorderlist/utils/profilescombobox.cpp b/components/fileorderlist/utils/profilescombobox.cpp index c3ff953ae0..9346276dae 100644 --- a/components/fileorderlist/utils/profilescombobox.cpp +++ b/components/fileorderlist/utils/profilescombobox.cpp @@ -90,3 +90,19 @@ void ProfilesComboBox::slotIndexChanged(int index) emit(profileChanged(mOldProfile, currentText())); mOldProfile = itemText(index); } + +void ProfilesComboBox::paintEvent(QPaintEvent *) +{ + QStylePainter painter(this); + painter.setPen(palette().color(QPalette::Text)); + + // draw the combobox frame, focusrect and selected etc. + QStyleOptionComboBox opt; + initStyleOption(&opt); + painter.drawComplexControl(QStyle::CC_ComboBox, opt); + + // draw the icon and text + if (!opt.editable && currentIndex() == -1) // <<< we adjust the text displayed when nothing is selected + opt.currentText = tr("Select a game file..."); + painter.drawControl(QStyle::CE_ComboBoxLabel, opt); +} diff --git a/components/fileorderlist/utils/profilescombobox.hpp b/components/fileorderlist/utils/profilescombobox.hpp index 08ead9a7ab..55913d7fec 100644 --- a/components/fileorderlist/utils/profilescombobox.hpp +++ b/components/fileorderlist/utils/profilescombobox.hpp @@ -2,7 +2,7 @@ #define PROFILESCOMBOBOX_HPP #include - +#include class QString; class QRegExpValidator; @@ -25,6 +25,9 @@ private slots: private: QString mOldProfile; QRegExpValidator *mValidator; + +protected: + void paintEvent(QPaintEvent *); }; #endif // PROFILESCOMBOBOX_HPP diff --git a/files/ui/datafilespage.ui b/files/ui/datafilespage.ui index 041a9576d0..342e4d9e90 100644 --- a/files/ui/datafilespage.ui +++ b/files/ui/datafilespage.ui @@ -14,28 +14,12 @@ - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Filter: + + + false - - - @@ -151,11 +135,6 @@ - - LineEdit - QLineEdit -
components/fileorderlist/utils/lineedit.hpp
-
ProfilesComboBox QComboBox From 49c4e1bf9eccd1efd91c371a22e0810a5c8cb815 Mon Sep 17 00:00:00 2001 From: graffy76 Date: Fri, 16 Aug 2013 18:23:02 -0500 Subject: [PATCH 003/434] Removed master table widget --- apps/launcher/datafilespage.cpp | 34 +++++++++++++++-------------- apps/opencs/view/doc/filedialog.cpp | 9 ++++---- files/ui/datafilespage.ui | 1 - 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 1fafd59226..077f3c292f 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -47,7 +47,7 @@ DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gam QCheckBox checkBox; unsigned int height = checkBox.sizeHint().height() + 4; - +/* mastersTable->setModel(mMastersProxyModel); mastersTable->setObjectName("MastersTable"); mastersTable->setContextMenuPolicy(Qt::CustomContextMenu); @@ -63,7 +63,7 @@ DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gam mastersTable->verticalHeader()->setDefaultSectionSize(height); mastersTable->verticalHeader()->setResizeMode(QHeaderView::Fixed); mastersTable->verticalHeader()->hide(); - +*/ pluginsTable->setModel(mFilterProxyModel); pluginsTable->setObjectName("PluginsTable"); pluginsTable->setContextMenuPolicy(Qt::CustomContextMenu); @@ -94,10 +94,10 @@ DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gam connect(mNewProfileDialog->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(updateOkButton(QString))); connect(pluginsTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(setCheckState(QModelIndex))); - connect(mastersTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(setCheckState(QModelIndex))); + //connect(mastersTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(setCheckState(QModelIndex))); connect(pluginsTable, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint))); - connect(mastersTable, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint))); + //connect(mastersTable, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint))); connect(mDataFilesModel, SIGNAL(layoutChanged()), this, SLOT(updateViews())); @@ -255,6 +255,7 @@ void DataFilesPage::updateSplitter() void DataFilesPage::updateViews() { // Ensure the columns are hidden because sort() re-enables them + /* mastersTable->setColumnHidden(1, true); mastersTable->setColumnHidden(2, true); mastersTable->setColumnHidden(3, true); @@ -263,7 +264,7 @@ void DataFilesPage::updateViews() mastersTable->setColumnHidden(6, true); mastersTable->setColumnHidden(7, true); mastersTable->setColumnHidden(8, true); - +*/ pluginsTable->setColumnHidden(1, true); pluginsTable->setColumnHidden(2, true); pluginsTable->setColumnHidden(3, true); @@ -335,8 +336,8 @@ void DataFilesPage::on_checkAction_triggered() if (pluginsTable->hasFocus()) setPluginsCheckstates(Qt::Checked); - if (mastersTable->hasFocus()) - setMastersCheckstates(Qt::Checked); + //if (mastersTable->hasFocus()) + // setMastersCheckstates(Qt::Checked); } @@ -345,17 +346,17 @@ void DataFilesPage::on_uncheckAction_triggered() if (pluginsTable->hasFocus()) setPluginsCheckstates(Qt::Unchecked); - if (mastersTable->hasFocus()) - setMastersCheckstates(Qt::Unchecked); + //if (mastersTable->hasFocus()) + // setMastersCheckstates(Qt::Unchecked); } void DataFilesPage::setMastersCheckstates(Qt::CheckState state) -{ - if (!mastersTable->selectionModel()->hasSelection()) { - return; - } +{/* + //if (!mastersTable->selectionModel()->hasSelection()) { + // return; + //} - QModelIndexList indexes = mastersTable->selectionModel()->selectedIndexes(); + //QModelIndexList indexes = mastersTable->selectionModel()->selectedIndexes(); foreach (const QModelIndex &index, indexes) { @@ -368,7 +369,7 @@ void DataFilesPage::setMastersCheckstates(Qt::CheckState state) return; mDataFilesModel->setCheckState(sourceIndex, state); - } + }*/ } void DataFilesPage::setPluginsCheckstates(Qt::CheckState state) @@ -519,7 +520,7 @@ void DataFilesPage::showContextMenu(const QPoint &point) // Show menu mContextMenu->exec(globalPos); } - +/* if (object->objectName() == QLatin1String("MastersTable")) { if (!mastersTable->selectionModel()->hasSelection()) return; @@ -548,4 +549,5 @@ void DataFilesPage::showContextMenu(const QPoint &point) mContextMenu->exec(globalPos); } + */ } diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index b06c970085..a07b854df6 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -42,7 +42,7 @@ FileDialog::FileDialog(QWidget *parent) : unsigned int height = checkBox.sizeHint().height() + 4; masterView->setModel(mMastersProxyModel); - +/* mastersTable->setModel(mMastersProxyModel); mastersTable->setObjectName("MastersTable"); mastersTable->setContextMenuPolicy(Qt::CustomContextMenu); @@ -57,7 +57,7 @@ FileDialog::FileDialog(QWidget *parent) : mastersTable->verticalHeader()->setDefaultSectionSize(height); mastersTable->verticalHeader()->setResizeMode(QHeaderView::Fixed); mastersTable->verticalHeader()->hide(); - +*/ pluginsTable->setModel(mFilterProxyModel); pluginsTable->setObjectName("PluginsTable"); pluginsTable->setContextMenuPolicy(Qt::CustomContextMenu); @@ -116,7 +116,7 @@ FileDialog::FileDialog(QWidget *parent) : //connect(filterLineEdit, SIGNAL(textChanged(QString)), this, SLOT(filterChanged(QString))); connect(pluginsTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(setCheckState(QModelIndex))); - connect(mastersTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(setCheckState(QModelIndex))); + //connect(mastersTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(setCheckState(QModelIndex))); connect(mCreateButton, SIGNAL(clicked()), this, SLOT(createButtonClicked())); @@ -127,6 +127,7 @@ FileDialog::FileDialog(QWidget *parent) : void FileDialog::updateViews() { // Ensure the columns are hidden because sort() re-enables them + /* mastersTable->setColumnHidden(1, true); mastersTable->setColumnHidden(3, true); mastersTable->setColumnHidden(4, true); @@ -135,7 +136,7 @@ void FileDialog::updateViews() mastersTable->setColumnHidden(7, true); mastersTable->setColumnHidden(8, true); mastersTable->resizeColumnsToContents(); - +*/ pluginsTable->setColumnHidden(1, true); pluginsTable->setColumnHidden(3, true); pluginsTable->setColumnHidden(4, true); diff --git a/files/ui/datafilespage.ui b/files/ui/datafilespage.ui index 342e4d9e90..816d288a65 100644 --- a/files/ui/datafilespage.ui +++ b/files/ui/datafilespage.ui @@ -36,7 +36,6 @@ false -
From b850fe02895dca508685d1ebbabb647f1e71c048 Mon Sep 17 00:00:00 2001 From: graffy76 Date: Fri, 16 Aug 2013 18:59:01 -0500 Subject: [PATCH 004/434] Removed vertical headers from plugin view --- apps/launcher/datafilespage.cpp | 6 ++++-- components/fileorderlist/model/datafilesmodel.cpp | 4 ++-- components/fileorderlist/model/pluginsproxymodel.cpp | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 077f3c292f..fe2fe82556 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -32,12 +32,14 @@ DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gam mDataFilesModel = new DataFilesModel(this); mMastersProxyModel = new QSortFilterProxyModel(); - mMastersProxyModel->setFilterRegExp(QString("^.*\\.esm")); + mMastersProxyModel->setFilterRegExp(QString("game")); //QString("^.*\\.esm")); + mMastersProxyModel->setFilterRole (Qt::UserRole); mMastersProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); mMastersProxyModel->setSourceModel(mDataFilesModel); mPluginsProxyModel = new PluginsProxyModel(); - mPluginsProxyModel->setFilterRegExp(QString("^.*\\.esp")); + mPluginsProxyModel->setFilterRegExp(QString("addon")); //^.*\\.esp")); + mPluginsProxyModel->setFilterRole (Qt::UserRole); mPluginsProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); mPluginsProxyModel->setSourceModel(mDataFilesModel); diff --git a/components/fileorderlist/model/datafilesmodel.cpp b/components/fileorderlist/model/datafilesmodel.cpp index cf1fa1b0a5..99c1aaebf4 100644 --- a/components/fileorderlist/model/datafilesmodel.cpp +++ b/components/fileorderlist/model/datafilesmodel.cpp @@ -202,11 +202,11 @@ QVariant DataFilesModel::headerData(int section, Qt::Orientation orientation, in case 7: return tr("Masters"); case 8: return tr("Description"); } - } else { + } /* else { // Show row numbers return ++section; } - +*/ return QVariant(); } diff --git a/components/fileorderlist/model/pluginsproxymodel.cpp b/components/fileorderlist/model/pluginsproxymodel.cpp index 6be152b555..726a3f158b 100644 --- a/components/fileorderlist/model/pluginsproxymodel.cpp +++ b/components/fileorderlist/model/pluginsproxymodel.cpp @@ -11,7 +11,7 @@ PluginsProxyModel::~PluginsProxyModel() QVariant PluginsProxyModel::headerData(int section, Qt::Orientation orientation, int role) const { - if (orientation != Qt::Vertical || role != Qt::DisplayRole) + //if (orientation != Qt::Vertical || role != Qt::DisplayRole) return QSortFilterProxyModel::headerData(section, orientation, role); - return section + 1; + // return section + 1; } From 2bc56d0b5c092a2e230281d55da9bdcecbc7e81c Mon Sep 17 00:00:00 2001 From: graffy76 Date: Fri, 16 Aug 2013 20:59:58 -0500 Subject: [PATCH 005/434] Fixed missing item list in launcher combobox --- apps/launcher/datafilespage.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index fe2fe82556..ddeb43ab7a 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -47,6 +47,8 @@ DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gam mFilterProxyModel->setDynamicSortFilter(true); mFilterProxyModel->setSourceModel(mPluginsProxyModel); + masterView->setModel (mMastersProxyModel); + QCheckBox checkBox; unsigned int height = checkBox.sizeHint().height() + 4; /* From 7389507eb52d2dadda0eba4f52624eb2d6f63360 Mon Sep 17 00:00:00 2001 From: graffy76 Date: Fri, 16 Aug 2013 21:12:30 -0500 Subject: [PATCH 006/434] Created masterproxylist class --- apps/launcher/datafilespage.cpp | 7 ++++--- apps/opencs/view/doc/filedialog.cpp | 4 +++- components/CMakeLists.txt | 1 + components/fileorderlist/masterproxymodel.cpp | 11 +++++++++++ components/fileorderlist/masterproxymodel.hpp | 19 +++++++++++++++++++ .../fileorderlist/model/pluginsproxymodel.cpp | 6 ++---- .../fileorderlist/model/pluginsproxymodel.hpp | 2 +- 7 files changed, 41 insertions(+), 9 deletions(-) create mode 100644 components/fileorderlist/masterproxymodel.cpp create mode 100644 components/fileorderlist/masterproxymodel.hpp diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index ddeb43ab7a..f674e9dc79 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -48,9 +48,10 @@ DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gam mFilterProxyModel->setSourceModel(mPluginsProxyModel); masterView->setModel (mMastersProxyModel); - +/* QCheckBox checkBox; unsigned int height = checkBox.sizeHint().height() + 4; + */ /* mastersTable->setModel(mMastersProxyModel); mastersTable->setObjectName("MastersTable"); @@ -80,8 +81,8 @@ DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gam pluginsTable->horizontalHeader()->setStretchLastSection(true); pluginsTable->horizontalHeader()->hide(); - pluginsTable->verticalHeader()->setDefaultSectionSize(height); - pluginsTable->verticalHeader()->setResizeMode(QHeaderView::Fixed); + //pluginsTable->verticalHeader()->setDefaultSectionSize(height); + //pluginsTable->verticalHeader()->setResizeMode(QHeaderView::Fixed); // Adjust the tableview widths inside the splitter QList sizeList; diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index a07b854df6..d49906949c 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -16,6 +16,8 @@ #include +#include "components/fileorderlist/masterproxymodel.hpp" + FileDialog::FileDialog(QWidget *parent) : QDialog(parent) { @@ -24,7 +26,7 @@ FileDialog::FileDialog(QWidget *parent) : // Models mDataFilesModel = new DataFilesModel(this); - mMastersProxyModel = new QSortFilterProxyModel(); + mMastersProxyModel = new MasterProxyModel(); mMastersProxyModel->setFilterRegExp("game"); //QString("^.*\\.esm")); mMastersProxyModel->setFilterRole (Qt::UserRole); mMastersProxyModel->setSourceModel(mDataFilesModel); diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 529891b4cb..bbaca48054 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -70,6 +70,7 @@ find_package(Qt4 COMPONENTS QtCore QtGui) if(QT_QTGUI_LIBRARY AND QT_QTCORE_LIBRARY) add_component_qt_dir (fileorderlist + masterproxymodel model/modelitem model/datafilesmodel model/pluginsproxymodel model/esm/esmfile utils/profilescombobox utils/comboboxlineedit utils/lineedit utils/naturalsort ) diff --git a/components/fileorderlist/masterproxymodel.cpp b/components/fileorderlist/masterproxymodel.cpp new file mode 100644 index 0000000000..ce874318d9 --- /dev/null +++ b/components/fileorderlist/masterproxymodel.cpp @@ -0,0 +1,11 @@ +#include "masterproxymodel.hpp" + +MasterProxyModel::MasterProxyModel(QObject *parent) : + QSortFilterProxyModel(parent) +{ +} + +QVariant MasterProxyModel::data(const QModelIndex &index, int role) const +{ + return QSortFilterProxyModel::data (index, role); +} diff --git a/components/fileorderlist/masterproxymodel.hpp b/components/fileorderlist/masterproxymodel.hpp new file mode 100644 index 0000000000..d0d2888730 --- /dev/null +++ b/components/fileorderlist/masterproxymodel.hpp @@ -0,0 +1,19 @@ +#ifndef MASTERPROXYMODEL_HPP +#define MASTERPROXYMODEL_HPP + +#include + +class MasterProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT +public: + explicit MasterProxyModel(QObject *parent = 0); + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + +signals: + +public slots: + +}; + +#endif // MASTERPROXYMODEL_HPP diff --git a/components/fileorderlist/model/pluginsproxymodel.cpp b/components/fileorderlist/model/pluginsproxymodel.cpp index 726a3f158b..4648d28330 100644 --- a/components/fileorderlist/model/pluginsproxymodel.cpp +++ b/components/fileorderlist/model/pluginsproxymodel.cpp @@ -9,9 +9,7 @@ PluginsProxyModel::~PluginsProxyModel() { } -QVariant PluginsProxyModel::headerData(int section, Qt::Orientation orientation, int role) const +QVariant PluginsProxyModel::data(const QModelIndex &index, int role) const { - //if (orientation != Qt::Vertical || role != Qt::DisplayRole) - return QSortFilterProxyModel::headerData(section, orientation, role); - // return section + 1; + return QSortFilterProxyModel::data (index, role); } diff --git a/components/fileorderlist/model/pluginsproxymodel.hpp b/components/fileorderlist/model/pluginsproxymodel.hpp index 8fde732361..08baa2338c 100644 --- a/components/fileorderlist/model/pluginsproxymodel.hpp +++ b/components/fileorderlist/model/pluginsproxymodel.hpp @@ -12,7 +12,7 @@ public: explicit PluginsProxyModel(QObject *parent = 0); ~PluginsProxyModel(); - QVariant headerData(int section, Qt::Orientation orientation, int role) const; + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; }; #endif // PLUGINSPROXYMODEL_HPP From 4c8c6d697119c84d6ac27233cb480483074d6f66 Mon Sep 17 00:00:00 2001 From: graffy76 Date: Fri, 16 Aug 2013 21:20:48 -0500 Subject: [PATCH 007/434] Moved init code to master / plugin proxy classes --- apps/launcher/datafilespage.cpp | 15 ++++---------- apps/opencs/view/doc/filedialog.cpp | 20 +++++++------------ components/fileorderlist/masterproxymodel.cpp | 7 ++++++- components/fileorderlist/masterproxymodel.hpp | 4 +++- .../fileorderlist/model/pluginsproxymodel.cpp | 7 ++++++- .../fileorderlist/model/pluginsproxymodel.hpp | 3 ++- 6 files changed, 28 insertions(+), 28 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index f674e9dc79..42e10c76b1 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -15,6 +15,7 @@ #include #include +#include "a.out.h" #include "settings/gamesettings.hpp" #include "settings/launchersettings.hpp" @@ -29,19 +30,11 @@ DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gam setupUi(this); // Models - mDataFilesModel = new DataFilesModel(this); + mDataFilesModel = new DataFilesModel (this); - mMastersProxyModel = new QSortFilterProxyModel(); - mMastersProxyModel->setFilterRegExp(QString("game")); //QString("^.*\\.esm")); - mMastersProxyModel->setFilterRole (Qt::UserRole); - mMastersProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); - mMastersProxyModel->setSourceModel(mDataFilesModel); + mMastersProxyModel = new MasterProxyModel (this, mDataFilesModel); - mPluginsProxyModel = new PluginsProxyModel(); - mPluginsProxyModel->setFilterRegExp(QString("addon")); //^.*\\.esp")); - mPluginsProxyModel->setFilterRole (Qt::UserRole); - mPluginsProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); - mPluginsProxyModel->setSourceModel(mDataFilesModel); + mPluginsProxyModel = new PluginsProxyModel (this, mDataFilesModel); mFilterProxyModel = new QSortFilterProxyModel(); mFilterProxyModel->setDynamicSortFilter(true); diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index d49906949c..dd94b05717 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -24,25 +24,19 @@ FileDialog::FileDialog(QWidget *parent) : setupUi(this); // Models - mDataFilesModel = new DataFilesModel(this); + mDataFilesModel = new DataFilesModel (this); - mMastersProxyModel = new MasterProxyModel(); - mMastersProxyModel->setFilterRegExp("game"); //QString("^.*\\.esm")); - mMastersProxyModel->setFilterRole (Qt::UserRole); - mMastersProxyModel->setSourceModel(mDataFilesModel); + mMastersProxyModel = new MasterProxyModel (this, mDataFilesModel); + mPluginsProxyModel = new PluginsProxyModel (this, mDataFilesModel); - mPluginsProxyModel = new PluginsProxyModel(); - mPluginsProxyModel->setFilterRegExp("addon"); //QString("^.*\\.esp")); - mPluginsProxyModel->setFilterRole (Qt::UserRole); - mPluginsProxyModel->setSourceModel(mDataFilesModel); mFilterProxyModel = new QSortFilterProxyModel(); mFilterProxyModel->setDynamicSortFilter(true); mFilterProxyModel->setSourceModel(mPluginsProxyModel); - +/* QCheckBox checkBox; unsigned int height = checkBox.sizeHint().height() + 4; - +*/ masterView->setModel(mMastersProxyModel); /* mastersTable->setModel(mMastersProxyModel); @@ -70,10 +64,10 @@ FileDialog::FileDialog(QWidget *parent) : pluginsTable->setAlternatingRowColors(true); pluginsTable->setVerticalScrollMode(QAbstractItemView::ScrollPerItem); pluginsTable->horizontalHeader()->setStretchLastSection(true); - +/* pluginsTable->verticalHeader()->setDefaultSectionSize(height); pluginsTable->verticalHeader()->setResizeMode(QHeaderView::Fixed); - +*/ // Hide the profile elements profileLabel->hide(); profilesComboBox->hide(); diff --git a/components/fileorderlist/masterproxymodel.cpp b/components/fileorderlist/masterproxymodel.cpp index ce874318d9..dc702e9457 100644 --- a/components/fileorderlist/masterproxymodel.cpp +++ b/components/fileorderlist/masterproxymodel.cpp @@ -1,8 +1,13 @@ #include "masterproxymodel.hpp" -MasterProxyModel::MasterProxyModel(QObject *parent) : +MasterProxyModel::MasterProxyModel(QObject *parent, QAbstractTableModel* model) : QSortFilterProxyModel(parent) { + setFilterRegExp(QString("game")); + setFilterRole (Qt::UserRole); + + if (model) + setSourceModel (model); } QVariant MasterProxyModel::data(const QModelIndex &index, int role) const diff --git a/components/fileorderlist/masterproxymodel.hpp b/components/fileorderlist/masterproxymodel.hpp index d0d2888730..d9e12dada9 100644 --- a/components/fileorderlist/masterproxymodel.hpp +++ b/components/fileorderlist/masterproxymodel.hpp @@ -3,11 +3,13 @@ #include +class QAbstractTableModel; + class MasterProxyModel : public QSortFilterProxyModel { Q_OBJECT public: - explicit MasterProxyModel(QObject *parent = 0); + explicit MasterProxyModel(QObject *parent = 0, QAbstractTableModel *model = 0); virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; signals: diff --git a/components/fileorderlist/model/pluginsproxymodel.cpp b/components/fileorderlist/model/pluginsproxymodel.cpp index 4648d28330..61ffb88946 100644 --- a/components/fileorderlist/model/pluginsproxymodel.cpp +++ b/components/fileorderlist/model/pluginsproxymodel.cpp @@ -1,8 +1,13 @@ #include "pluginsproxymodel.hpp" -PluginsProxyModel::PluginsProxyModel(QObject *parent) : +PluginsProxyModel::PluginsProxyModel(QObject *parent, QAbstractTableModel *model) : QSortFilterProxyModel(parent) { + setFilterRegExp(QString("addon")); + setFilterRole (Qt::UserRole); + + if (model) + setSourceModel (model); } PluginsProxyModel::~PluginsProxyModel() diff --git a/components/fileorderlist/model/pluginsproxymodel.hpp b/components/fileorderlist/model/pluginsproxymodel.hpp index 08baa2338c..238d2aac77 100644 --- a/components/fileorderlist/model/pluginsproxymodel.hpp +++ b/components/fileorderlist/model/pluginsproxymodel.hpp @@ -4,12 +4,13 @@ #include class QVariant; +class QAbstractTableModel; class PluginsProxyModel : public QSortFilterProxyModel { Q_OBJECT public: - explicit PluginsProxyModel(QObject *parent = 0); + explicit PluginsProxyModel(QObject *parent = 0, QAbstractTableModel *model = 0); ~PluginsProxyModel(); virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; From 61602789e1092eb80bdcb677f03ce7a02fce6501 Mon Sep 17 00:00:00 2001 From: graffy76 Date: Fri, 16 Aug 2013 22:23:21 -0500 Subject: [PATCH 008/434] Began migrating code to ContentSelector base --- apps/launcher/datafilespage.cpp | 21 +-- apps/launcher/datafilespage.hpp | 4 +- apps/opencs/view/doc/filedialog.cpp | 117 +++------------- apps/opencs/view/doc/filedialog.hpp | 12 +- components/CMakeLists.txt | 9 +- components/fileorderlist/contentselector.cpp | 132 ++++++++++++++++++ components/fileorderlist/contentselector.hpp | 40 ++++++ components/fileorderlist/masterproxymodel.cpp | 4 +- components/fileorderlist/masterproxymodel.hpp | 22 +-- 9 files changed, 232 insertions(+), 129 deletions(-) create mode 100644 components/fileorderlist/contentselector.cpp create mode 100644 components/fileorderlist/contentselector.hpp diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 42e10c76b1..7a48900518 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -15,7 +15,7 @@ #include #include -#include "a.out.h" +#include "components/fileorderlist/masterproxymodel.hpp" #include "settings/gamesettings.hpp" #include "settings/launchersettings.hpp" @@ -25,10 +25,11 @@ DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gam : mCfgMgr(cfg) , mGameSettings(gameSettings) , mLauncherSettings(launcherSettings) - , QWidget(parent) + , ContentSelector(parent) { setupUi(this); - + buildModelsAndViews(); + /* // Models mDataFilesModel = new DataFilesModel (this); @@ -41,11 +42,11 @@ DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gam mFilterProxyModel->setSourceModel(mPluginsProxyModel); masterView->setModel (mMastersProxyModel); -/* - QCheckBox checkBox; - unsigned int height = checkBox.sizeHint().height() + 4; - */ -/* + + //QCheckBox checkBox; + // unsigned int height = checkBox.sizeHint().height() + 4; + + mastersTable->setModel(mMastersProxyModel); mastersTable->setObjectName("MastersTable"); mastersTable->setContextMenuPolicy(Qt::CustomContextMenu); @@ -61,7 +62,7 @@ DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gam mastersTable->verticalHeader()->setDefaultSectionSize(height); mastersTable->verticalHeader()->setResizeMode(QHeaderView::Fixed); mastersTable->verticalHeader()->hide(); -*/ + pluginsTable->setModel(mFilterProxyModel); pluginsTable->setObjectName("PluginsTable"); pluginsTable->setContextMenuPolicy(Qt::CustomContextMenu); @@ -76,7 +77,7 @@ DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gam //pluginsTable->verticalHeader()->setDefaultSectionSize(height); //pluginsTable->verticalHeader()->setResizeMode(QHeaderView::Fixed); - +*/ // Adjust the tableview widths inside the splitter QList sizeList; sizeList << mLauncherSettings.value(QString("General/MastersTable/width"), QString("200")).toInt(); diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index a0b0293309..99aa24ff3c 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -5,6 +5,7 @@ #include #include "ui_datafilespage.h" +#include "components/fileorderlist/contentselector.hpp" class QSortFilterProxyModel; class QAbstractItemModel; @@ -19,10 +20,9 @@ class PluginsProxyModel; namespace Files { struct ConfigurationManager; } -class DataFilesPage : public QWidget, private Ui::DataFilesPage +class DataFilesPage : public FileOrderList::ContentSelector { Q_OBJECT - public: DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gameSettings, LauncherSettings &launcherSettings, QWidget *parent = 0); diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index dd94b05717..f770e80a1e 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -19,10 +19,11 @@ #include "components/fileorderlist/masterproxymodel.hpp" FileDialog::FileDialog(QWidget *parent) : - QDialog(parent) + ContentSelector(parent) { setupUi(this); - + buildModelsAndViews(); + /* // Models mDataFilesModel = new DataFilesModel (this); @@ -33,12 +34,12 @@ FileDialog::FileDialog(QWidget *parent) : mFilterProxyModel = new QSortFilterProxyModel(); mFilterProxyModel->setDynamicSortFilter(true); mFilterProxyModel->setSourceModel(mPluginsProxyModel); -/* - QCheckBox checkBox; - unsigned int height = checkBox.sizeHint().height() + 4; -*/ + +// QCheckBox checkBox; +// unsigned int height = checkBox.sizeHint().height() + 4; + masterView->setModel(mMastersProxyModel); -/* + mastersTable->setModel(mMastersProxyModel); mastersTable->setObjectName("MastersTable"); mastersTable->setContextMenuPolicy(Qt::CustomContextMenu); @@ -53,7 +54,7 @@ FileDialog::FileDialog(QWidget *parent) : mastersTable->verticalHeader()->setDefaultSectionSize(height); mastersTable->verticalHeader()->setResizeMode(QHeaderView::Fixed); mastersTable->verticalHeader()->hide(); -*/ + pluginsTable->setModel(mFilterProxyModel); pluginsTable->setObjectName("PluginsTable"); pluginsTable->setContextMenuPolicy(Qt::CustomContextMenu); @@ -64,10 +65,11 @@ FileDialog::FileDialog(QWidget *parent) : pluginsTable->setAlternatingRowColors(true); pluginsTable->setVerticalScrollMode(QAbstractItemView::ScrollPerItem); pluginsTable->horizontalHeader()->setStretchLastSection(true); -/* - pluginsTable->verticalHeader()->setDefaultSectionSize(height); - pluginsTable->verticalHeader()->setResizeMode(QHeaderView::Fixed); -*/ + +// pluginsTable->verticalHeader()->setDefaultSectionSize(height); +// pluginsTable->verticalHeader()->setResizeMode(QHeaderView::Fixed); + + */ // Hide the profile elements profileLabel->hide(); profilesComboBox->hide(); @@ -105,43 +107,19 @@ FileDialog::FileDialog(QWidget *parent) : resize(600, 400); - connect(mDataFilesModel, SIGNAL(layoutChanged()), this, SLOT(updateViews())); - connect(mDataFilesModel, SIGNAL(checkedItemsChanged(QStringList)), this, SLOT(updateOpenButton(QStringList))); + // + // connect(mDataFilesModel, SIGNAL(checkedItemsChanged(QStringList)), this, SLOT(updateOpenButton(QStringList))); //connect(mNameLineEdit, SIGNAL(textChanged(QString)), this, SLOT(updateCreateButton(QString))); //connect(filterLineEdit, SIGNAL(textChanged(QString)), this, SLOT(filterChanged(QString))); - connect(pluginsTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(setCheckState(QModelIndex))); + // connect(pluginsTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(setCheckState(QModelIndex))); //connect(mastersTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(setCheckState(QModelIndex))); - connect(mCreateButton, SIGNAL(clicked()), this, SLOT(createButtonClicked())); - - connect(mButtonBox, SIGNAL(accepted()), this, SLOT(accept())); - connect(mButtonBox, SIGNAL(rejected()), this, SLOT(reject())); -} - -void FileDialog::updateViews() -{ - // Ensure the columns are hidden because sort() re-enables them - /* - mastersTable->setColumnHidden(1, true); - mastersTable->setColumnHidden(3, true); - mastersTable->setColumnHidden(4, true); - mastersTable->setColumnHidden(5, true); - mastersTable->setColumnHidden(6, true); - mastersTable->setColumnHidden(7, true); - mastersTable->setColumnHidden(8, true); - mastersTable->resizeColumnsToContents(); -*/ - pluginsTable->setColumnHidden(1, true); - pluginsTable->setColumnHidden(3, true); - pluginsTable->setColumnHidden(4, true); - pluginsTable->setColumnHidden(5, true); - pluginsTable->setColumnHidden(6, true); - pluginsTable->setColumnHidden(7, true); - pluginsTable->setColumnHidden(8, true); - pluginsTable->resizeColumnsToContents(); + // connect(mCreateButton, SIGNAL(clicked()), this, SLOT(createButtonClicked())); + // connect(mButtonBox, SIGNAL(accepted()), this, SLOT(accept())); + // connect(mButtonBox, SIGNAL(rejected()), this, SLOT(reject())); } void FileDialog::updateOpenButton(const QStringList &items) @@ -161,64 +139,13 @@ void FileDialog::updateCreateButton(const QString &name) mCreateButton->setEnabled(!name.isEmpty()); } - +/* void FileDialog::filterChanged(const QString &filter) { QRegExp filterRe(filter, Qt::CaseInsensitive, QRegExp::FixedString); mFilterProxyModel->setFilterRegExp(filterRe); } - -void FileDialog::addFiles(const QString &path) -{ - mDataFilesModel->addFiles(path); - mDataFilesModel->sort(3); // Sort by date accessed -} - -void FileDialog::setEncoding(const QString &encoding) -{ - mDataFilesModel->setEncoding(encoding); -} - -void FileDialog::setCheckState(QModelIndex index) -{ - if (!index.isValid()) - return; - - QObject *object = QObject::sender(); - - // Not a signal-slot call - if (!object) - return; - - - if (object->objectName() == QLatin1String("PluginsTable")) { - QModelIndex sourceIndex = mPluginsProxyModel->mapToSource( - mFilterProxyModel->mapToSource(index)); - - if (sourceIndex.isValid()) { - (mDataFilesModel->checkState(sourceIndex) == Qt::Checked) - ? mDataFilesModel->setCheckState(sourceIndex, Qt::Unchecked) - : mDataFilesModel->setCheckState(sourceIndex, Qt::Checked); - } - } - - if (object->objectName() == QLatin1String("MastersTable")) { - QModelIndex sourceIndex = mMastersProxyModel->mapToSource(index); - - if (sourceIndex.isValid()) { - (mDataFilesModel->checkState(sourceIndex) == Qt::Checked) - ? mDataFilesModel->setCheckState(sourceIndex, Qt::Unchecked) - : mDataFilesModel->setCheckState(sourceIndex, Qt::Checked); - } - } - - return; -} - -QStringList FileDialog::checkedItemsPaths() -{ - return mDataFilesModel->checkedItemsPaths(); -} +*/ QString FileDialog::fileName() { diff --git a/apps/opencs/view/doc/filedialog.hpp b/apps/opencs/view/doc/filedialog.hpp index 4c3fe9ffab..2f4d4e381d 100644 --- a/apps/opencs/view/doc/filedialog.hpp +++ b/apps/opencs/view/doc/filedialog.hpp @@ -4,6 +4,7 @@ #include #include +#include "components/fileorderlist/contentselector.hpp" #include "ui_datafilespage.h" class QDialogButtonBox; @@ -17,19 +18,16 @@ class QMenu; class DataFilesModel; class PluginsProxyModel; -class FileDialog : public QDialog, private Ui::DataFilesPage +class FileDialog : public FileOrderList::ContentSelector { Q_OBJECT public: explicit FileDialog(QWidget *parent = 0); - void addFiles(const QString &path); - void setEncoding(const QString &encoding); void openFile(); void newFile(); void accepted(); - QStringList checkedItemsPaths(); QString fileName(); signals: @@ -40,12 +38,12 @@ public slots: void accept(); private slots: - void updateViews(); + //void updateViews(); void updateOpenButton(const QStringList &items); void updateCreateButton(const QString &name); - void setCheckState(QModelIndex index); - void filterChanged(const QString &filter); + + //void filterChanged(const QString &filter); void createButtonClicked(); diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index bbaca48054..19af6c3b21 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -66,22 +66,25 @@ add_component_dir (translation translation ) +set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/datafilespage.ui + ) find_package(Qt4 COMPONENTS QtCore QtGui) if(QT_QTGUI_LIBRARY AND QT_QTCORE_LIBRARY) add_component_qt_dir (fileorderlist - masterproxymodel + masterproxymodel contentselector model/modelitem model/datafilesmodel model/pluginsproxymodel model/esm/esmfile utils/profilescombobox utils/comboboxlineedit utils/lineedit utils/naturalsort ) include(${QT_USE_FILE}) + QT4_WRAP_UI(ESM_UI_HDR ${ESM_UI}) QT4_WRAP_CPP(MOC_SRCS ${COMPONENT_MOC_FILES}) endif(QT_QTGUI_LIBRARY AND QT_QTCORE_LIBRARY) -include_directories(${BULLET_INCLUDE_DIRS}) +include_directories(${BULLET_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) -add_library(components STATIC ${COMPONENT_FILES} ${MOC_SRCS}) +add_library(components STATIC ${COMPONENT_FILES} ${MOC_SRCS} ${ESM_UI_HDR}) target_link_libraries(components ${Boost_LIBRARIES} ${OGRE_LIBRARIES}) diff --git a/components/fileorderlist/contentselector.cpp b/components/fileorderlist/contentselector.cpp new file mode 100644 index 0000000000..16e0718910 --- /dev/null +++ b/components/fileorderlist/contentselector.cpp @@ -0,0 +1,132 @@ +#include "contentselector.hpp" + +#include "model/datafilesmodel.hpp" +#include "masterproxymodel.hpp" +#include "model/pluginsproxymodel.hpp" + +#include + +FileOrderList::ContentSelector::ContentSelector(QWidget *parent) : + QWidget(parent) +{ +} + +void FileOrderList::ContentSelector::buildModelsAndViews() +{ + // Models + mDataFilesModel = new DataFilesModel (this); + + mMasterProxyModel = new FileOrderList::MasterProxyModel (this, mDataFilesModel); + mPluginsProxyModel = new PluginsProxyModel (this, mDataFilesModel); + + + mFilterProxyModel = new QSortFilterProxyModel(); + mFilterProxyModel->setDynamicSortFilter(true); + mFilterProxyModel->setSourceModel(mPluginsProxyModel); + + masterView->setModel(mMasterProxyModel); +/* + mastersTable->setModel(mMastersProxyModel); + mastersTable->setObjectName("MastersTable"); + mastersTable->setContextMenuPolicy(Qt::CustomContextMenu); + mastersTable->setSortingEnabled(false); + mastersTable->setSelectionBehavior(QAbstractItemView::SelectRows); + mastersTable->setSelectionMode(QAbstractItemView::ExtendedSelection); + mastersTable->setEditTriggers(QAbstractItemView::NoEditTriggers); + mastersTable->setAlternatingRowColors(true); + mastersTable->horizontalHeader()->setStretchLastSection(true); + + // Set the row height to the size of the checkboxes + mastersTable->verticalHeader()->setDefaultSectionSize(height); + mastersTable->verticalHeader()->setResizeMode(QHeaderView::Fixed); + mastersTable->verticalHeader()->hide(); +*/ + pluginsTable->setModel(mFilterProxyModel); + pluginsTable->setObjectName("PluginsTable"); + pluginsTable->setContextMenuPolicy(Qt::CustomContextMenu); + pluginsTable->setSortingEnabled(false); + pluginsTable->setSelectionBehavior(QAbstractItemView::SelectRows); + pluginsTable->setSelectionMode(QAbstractItemView::ExtendedSelection); + pluginsTable->setEditTriggers(QAbstractItemView::NoEditTriggers); + pluginsTable->setAlternatingRowColors(true); + pluginsTable->setVerticalScrollMode(QAbstractItemView::ScrollPerItem); + pluginsTable->horizontalHeader()->setStretchLastSection(true); + + connect(mDataFilesModel, SIGNAL(layoutChanged()), this, SLOT(updateViews())); +} + +void FileOrderList::ContentSelector::addFiles(const QString &path) +{ + mDataFilesModel->addFiles(path); + mDataFilesModel->sort(3); // Sort by date accessed +} + +void FileOrderList::ContentSelector::setEncoding(const QString &encoding) +{ + mDataFilesModel->setEncoding(encoding); +} + +void FileOrderList::ContentSelector::setCheckState(QModelIndex index) +{ + if (!index.isValid()) + return; + + QObject *object = QObject::sender(); + + // Not a signal-slot call + if (!object) + return; + + + if (object->objectName() == QLatin1String("PluginsTable")) { + QModelIndex sourceIndex = mPluginsProxyModel->mapToSource( + mFilterProxyModel->mapToSource(index)); + + if (sourceIndex.isValid()) { + (mDataFilesModel->checkState(sourceIndex) == Qt::Checked) + ? mDataFilesModel->setCheckState(sourceIndex, Qt::Unchecked) + : mDataFilesModel->setCheckState(sourceIndex, Qt::Checked); + } + } +/* + if (object->objectName() == QLatin1String("MastersTable")) { + QModelIndex sourceIndex = mMasterProxyModel->mapToSource(index); + + if (sourceIndex.isValid()) { + (mDataFilesModel->checkState(sourceIndex) == Qt::Checked) + ? mDataFilesModel->setCheckState(sourceIndex, Qt::Unchecked) + : mDataFilesModel->setCheckState(sourceIndex, Qt::Checked); + } + } +*/ + return; +} + +QStringList FileOrderList::ContentSelector::checkedItemsPaths() +{ + return mDataFilesModel->checkedItemsPaths(); +} + +void FileOrderList::ContentSelector::updateViews() +{ + // Ensure the columns are hidden because sort() re-enables them + /* + mastersTable->setColumnHidden(1, true); + mastersTable->setColumnHidden(3, true); + mastersTable->setColumnHidden(4, true); + mastersTable->setColumnHidden(5, true); + mastersTable->setColumnHidden(6, true); + mastersTable->setColumnHidden(7, true); + mastersTable->setColumnHidden(8, true); + mastersTable->resizeColumnsToContents(); +*/ + pluginsTable->setColumnHidden(1, true); + pluginsTable->setColumnHidden(3, true); + pluginsTable->setColumnHidden(4, true); + pluginsTable->setColumnHidden(5, true); + pluginsTable->setColumnHidden(6, true); + pluginsTable->setColumnHidden(7, true); + pluginsTable->setColumnHidden(8, true); + pluginsTable->resizeColumnsToContents(); + +} diff --git a/components/fileorderlist/contentselector.hpp b/components/fileorderlist/contentselector.hpp new file mode 100644 index 0000000000..f17e3a9090 --- /dev/null +++ b/components/fileorderlist/contentselector.hpp @@ -0,0 +1,40 @@ +#ifndef CONTENTSELECTOR_HPP +#define CONTENTSELECTOR_HPP + +#include + +#include "ui_datafilespage.h" + +class DataFilesModel; +class PluginsProxyModel; +class QSortFilterProxyModel; + +namespace FileOrderList +{ + class MasterProxyModel; + + class ContentSelector : public QWidget, protected Ui::DataFilesPage + { + Q_OBJECT + + DataFilesModel *mDataFilesModel; + MasterProxyModel *mMasterProxyModel; + PluginsProxyModel *mPluginsProxyModel; + QSortFilterProxyModel *mFilterProxyModel; + + public: + explicit ContentSelector(QWidget *parent = 0); + + void buildModelsAndViews(); + + void addFiles(const QString &path); + void setEncoding(const QString &encoding); + void setCheckState(QModelIndex index); + QStringList checkedItemsPaths(); + + private slots: + void updateViews(); + }; +} + +#endif // CONTENTSELECTOR_HPP diff --git a/components/fileorderlist/masterproxymodel.cpp b/components/fileorderlist/masterproxymodel.cpp index dc702e9457..7701d4f17a 100644 --- a/components/fileorderlist/masterproxymodel.cpp +++ b/components/fileorderlist/masterproxymodel.cpp @@ -1,6 +1,6 @@ #include "masterproxymodel.hpp" -MasterProxyModel::MasterProxyModel(QObject *parent, QAbstractTableModel* model) : +FileOrderList::MasterProxyModel::MasterProxyModel(QObject *parent, QAbstractTableModel* model) : QSortFilterProxyModel(parent) { setFilterRegExp(QString("game")); @@ -10,7 +10,7 @@ MasterProxyModel::MasterProxyModel(QObject *parent, QAbstractTableModel* model) setSourceModel (model); } -QVariant MasterProxyModel::data(const QModelIndex &index, int role) const +QVariant FileOrderList::MasterProxyModel::data(const QModelIndex &index, int role) const { return QSortFilterProxyModel::data (index, role); } diff --git a/components/fileorderlist/masterproxymodel.hpp b/components/fileorderlist/masterproxymodel.hpp index d9e12dada9..49ca369deb 100644 --- a/components/fileorderlist/masterproxymodel.hpp +++ b/components/fileorderlist/masterproxymodel.hpp @@ -5,17 +5,19 @@ class QAbstractTableModel; -class MasterProxyModel : public QSortFilterProxyModel +namespace FileOrderList { - Q_OBJECT -public: - explicit MasterProxyModel(QObject *parent = 0, QAbstractTableModel *model = 0); - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + class MasterProxyModel : public QSortFilterProxyModel + { + Q_OBJECT + public: + explicit MasterProxyModel(QObject *parent = 0, QAbstractTableModel *model = 0); + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; -signals: - -public slots: - -}; + signals: + public slots: + + }; +} #endif // MASTERPROXYMODEL_HPP From 0087b0d67c4b36cfb9e9e9ce5af5d282673d9419 Mon Sep 17 00:00:00 2001 From: graffy76 Date: Sat, 17 Aug 2013 05:37:23 -0500 Subject: [PATCH 009/434] Removed checkboxes from master list Moved checkbox code from datafilesmodel to pluginsproxymodel --- .../fileorderlist/model/datafilesmodel.cpp | 5 ----- .../fileorderlist/model/pluginsproxymodel.cpp | 16 ++++++++++++++-- .../fileorderlist/model/pluginsproxymodel.hpp | 7 ++++++- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/components/fileorderlist/model/datafilesmodel.cpp b/components/fileorderlist/model/datafilesmodel.cpp index 99c1aaebf4..3f63e73cce 100644 --- a/components/fileorderlist/model/datafilesmodel.cpp +++ b/components/fileorderlist/model/datafilesmodel.cpp @@ -117,11 +117,6 @@ QVariant DataFilesModel::data(const QModelIndex &index, int role) const } } - case Qt::CheckStateRole: { - if (column != 0) - return QVariant(); - return mCheckStates[file->fileName()]; - } case Qt::ToolTipRole: { if (column != 0) diff --git a/components/fileorderlist/model/pluginsproxymodel.cpp b/components/fileorderlist/model/pluginsproxymodel.cpp index 61ffb88946..f6864d85cd 100644 --- a/components/fileorderlist/model/pluginsproxymodel.cpp +++ b/components/fileorderlist/model/pluginsproxymodel.cpp @@ -1,7 +1,8 @@ #include "pluginsproxymodel.hpp" +#include "datafilesmodel.hpp" -PluginsProxyModel::PluginsProxyModel(QObject *parent, QAbstractTableModel *model) : - QSortFilterProxyModel(parent) +PluginsProxyModel::PluginsProxyModel(QObject *parent, DataFilesModel *model) : + QSortFilterProxyModel(parent), mSourceModel (model) { setFilterRegExp(QString("addon")); setFilterRole (Qt::UserRole); @@ -16,5 +17,16 @@ PluginsProxyModel::~PluginsProxyModel() QVariant PluginsProxyModel::data(const QModelIndex &index, int role) const { + switch (role) + { + case Qt::CheckStateRole: + { + if (index.column() != 0) + return QVariant(); + + return mSourceModel->checkState(index); + } + }; + return QSortFilterProxyModel::data (index, role); } diff --git a/components/fileorderlist/model/pluginsproxymodel.hpp b/components/fileorderlist/model/pluginsproxymodel.hpp index 238d2aac77..e148ea3b16 100644 --- a/components/fileorderlist/model/pluginsproxymodel.hpp +++ b/components/fileorderlist/model/pluginsproxymodel.hpp @@ -5,12 +5,17 @@ class QVariant; class QAbstractTableModel; +class DataFilesModel; class PluginsProxyModel : public QSortFilterProxyModel { Q_OBJECT + + DataFilesModel *mSourceModel; + public: - explicit PluginsProxyModel(QObject *parent = 0, QAbstractTableModel *model = 0); + + explicit PluginsProxyModel(QObject *parent = 0, DataFilesModel *model = 0); ~PluginsProxyModel(); virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; From b24dd5c6acf9985b34aa9af45c24d817cc211a0c Mon Sep 17 00:00:00 2001 From: graffy76 Date: Sat, 17 Aug 2013 05:55:43 -0500 Subject: [PATCH 010/434] Continued migration of code to ContentSelector --- apps/launcher/datafilespage.cpp | 106 ++---------------- apps/launcher/datafilespage.hpp | 8 -- apps/opencs/view/doc/filedialog.cpp | 50 --------- apps/opencs/view/doc/filedialog.hpp | 6 - components/fileorderlist/contentselector.cpp | 26 +---- components/fileorderlist/contentselector.hpp | 3 +- .../fileorderlist/model/pluginsproxymodel.cpp | 3 +- 7 files changed, 17 insertions(+), 185 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 7a48900518..d077c3e0e9 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -21,63 +21,14 @@ #include "utils/textinputdialog.hpp" +#include + DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gameSettings, LauncherSettings &launcherSettings, QWidget *parent) : mCfgMgr(cfg) , mGameSettings(gameSettings) , mLauncherSettings(launcherSettings) , ContentSelector(parent) { - setupUi(this); - buildModelsAndViews(); - /* - // Models - mDataFilesModel = new DataFilesModel (this); - - mMastersProxyModel = new MasterProxyModel (this, mDataFilesModel); - - mPluginsProxyModel = new PluginsProxyModel (this, mDataFilesModel); - - mFilterProxyModel = new QSortFilterProxyModel(); - mFilterProxyModel->setDynamicSortFilter(true); - mFilterProxyModel->setSourceModel(mPluginsProxyModel); - - masterView->setModel (mMastersProxyModel); - - //QCheckBox checkBox; - // unsigned int height = checkBox.sizeHint().height() + 4; - - - mastersTable->setModel(mMastersProxyModel); - mastersTable->setObjectName("MastersTable"); - mastersTable->setContextMenuPolicy(Qt::CustomContextMenu); - mastersTable->setSortingEnabled(false); - mastersTable->setSelectionBehavior(QAbstractItemView::SelectRows); - mastersTable->setSelectionMode(QAbstractItemView::ExtendedSelection); - mastersTable->setEditTriggers(QAbstractItemView::NoEditTriggers); - mastersTable->setAlternatingRowColors(true); - mastersTable->horizontalHeader()->setStretchLastSection(true); - mastersTable->horizontalHeader()->hide(); - - // Set the row height to the size of the checkboxes - mastersTable->verticalHeader()->setDefaultSectionSize(height); - mastersTable->verticalHeader()->setResizeMode(QHeaderView::Fixed); - mastersTable->verticalHeader()->hide(); - - pluginsTable->setModel(mFilterProxyModel); - pluginsTable->setObjectName("PluginsTable"); - pluginsTable->setContextMenuPolicy(Qt::CustomContextMenu); - pluginsTable->setSortingEnabled(false); - pluginsTable->setSelectionBehavior(QAbstractItemView::SelectRows); - pluginsTable->setSelectionMode(QAbstractItemView::ExtendedSelection); - pluginsTable->setEditTriggers(QAbstractItemView::NoEditTriggers); - pluginsTable->setAlternatingRowColors(true); - pluginsTable->setVerticalScrollMode(QAbstractItemView::ScrollPerItem); - pluginsTable->horizontalHeader()->setStretchLastSection(true); - pluginsTable->horizontalHeader()->hide(); - - //pluginsTable->verticalHeader()->setDefaultSectionSize(height); - //pluginsTable->verticalHeader()->setResizeMode(QHeaderView::Fixed); -*/ // Adjust the tableview widths inside the splitter QList sizeList; sizeList << mLauncherSettings.value(QString("General/MastersTable/width"), QString("200")).toInt(); @@ -98,8 +49,6 @@ DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gam connect(pluginsTable, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint))); //connect(mastersTable, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint))); - connect(mDataFilesModel, SIGNAL(layoutChanged()), this, SLOT(updateViews())); - //connect(filterLineEdit, SIGNAL(textChanged(QString)), this, SLOT(filterChanged(QString))); connect(splitter, SIGNAL(splitterMoved(int,int)), this, SLOT(updateSplitter())); @@ -123,6 +72,9 @@ void DataFilesPage::createActions() void DataFilesPage::setupDataFiles() { + if (!mDataFilesModel) + qDebug() << "data files model undefined"; + // Set the encoding to the one found in openmw.cfg or the default mDataFilesModel->setEncoding(mGameSettings.value(QString("encoding"), QString("win1252"))); @@ -384,8 +336,7 @@ void DataFilesPage::setPluginsCheckstates(Qt::CheckState state) if (!index.isValid()) return; - QModelIndex sourceIndex = mPluginsProxyModel->mapToSource( - mFilterProxyModel->mapToSource(index)); + QModelIndex sourceIndex = mPluginsProxyModel->mapToSource(index); if (!sourceIndex.isValid()) return; @@ -394,48 +345,6 @@ void DataFilesPage::setPluginsCheckstates(Qt::CheckState state) } } -void DataFilesPage::setCheckState(QModelIndex index) -{ - if (!index.isValid()) - return; - - QObject *object = QObject::sender(); - - // Not a signal-slot call - if (!object) - return; - - - if (object->objectName() == QLatin1String("PluginsTable")) { - QModelIndex sourceIndex = mPluginsProxyModel->mapToSource( - mFilterProxyModel->mapToSource(index)); - - if (sourceIndex.isValid()) { - (mDataFilesModel->checkState(sourceIndex) == Qt::Checked) - ? mDataFilesModel->setCheckState(sourceIndex, Qt::Unchecked) - : mDataFilesModel->setCheckState(sourceIndex, Qt::Checked); - } - } - - if (object->objectName() == QLatin1String("MastersTable")) { - QModelIndex sourceIndex = mMastersProxyModel->mapToSource(index); - - if (sourceIndex.isValid()) { - (mDataFilesModel->checkState(sourceIndex) == Qt::Checked) - ? mDataFilesModel->setCheckState(sourceIndex, Qt::Unchecked) - : mDataFilesModel->setCheckState(sourceIndex, Qt::Checked); - } - } - - return; -} - -void DataFilesPage::filterChanged(const QString filter) -{ - QRegExp regExp(filter, Qt::CaseInsensitive, QRegExp::FixedString); - mFilterProxyModel->setFilterRegExp(regExp); -} - void DataFilesPage::profileChanged(const QString &previous, const QString ¤t) { // Prevent the deletion of the default profile @@ -505,8 +414,7 @@ void DataFilesPage::showContextMenu(const QPoint &point) if (!index.isValid()) return; - QModelIndex sourceIndex = mPluginsProxyModel->mapToSource( - mFilterProxyModel->mapToSource(index)); + QModelIndex sourceIndex = mPluginsProxyModel->mapToSource(index); if (!sourceIndex.isValid()) return; diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index 99aa24ff3c..1c528ab26e 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -36,10 +36,8 @@ signals: void profileChanged(int index); public slots: - void setCheckState(QModelIndex index); void setProfilesComboBoxIndex(int index); - void filterChanged(const QString filter); void showContextMenu(const QPoint &point); void profileChanged(const QString &previous, const QString ¤t); void profileRenamed(const QString &previous, const QString ¤t); @@ -57,12 +55,6 @@ private slots: void slotCurrentIndexChanged(int index); private: - DataFilesModel *mDataFilesModel; - - PluginsProxyModel *mPluginsProxyModel; - QSortFilterProxyModel *mMastersProxyModel; - - QSortFilterProxyModel *mFilterProxyModel; QMenu *mContextMenu; diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index f770e80a1e..2f3a6dc219 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -21,55 +21,6 @@ FileDialog::FileDialog(QWidget *parent) : ContentSelector(parent) { - setupUi(this); - buildModelsAndViews(); - /* - // Models - mDataFilesModel = new DataFilesModel (this); - - mMastersProxyModel = new MasterProxyModel (this, mDataFilesModel); - mPluginsProxyModel = new PluginsProxyModel (this, mDataFilesModel); - - - mFilterProxyModel = new QSortFilterProxyModel(); - mFilterProxyModel->setDynamicSortFilter(true); - mFilterProxyModel->setSourceModel(mPluginsProxyModel); - -// QCheckBox checkBox; -// unsigned int height = checkBox.sizeHint().height() + 4; - - masterView->setModel(mMastersProxyModel); - - mastersTable->setModel(mMastersProxyModel); - mastersTable->setObjectName("MastersTable"); - mastersTable->setContextMenuPolicy(Qt::CustomContextMenu); - mastersTable->setSortingEnabled(false); - mastersTable->setSelectionBehavior(QAbstractItemView::SelectRows); - mastersTable->setSelectionMode(QAbstractItemView::ExtendedSelection); - mastersTable->setEditTriggers(QAbstractItemView::NoEditTriggers); - mastersTable->setAlternatingRowColors(true); - mastersTable->horizontalHeader()->setStretchLastSection(true); - - // Set the row height to the size of the checkboxes - mastersTable->verticalHeader()->setDefaultSectionSize(height); - mastersTable->verticalHeader()->setResizeMode(QHeaderView::Fixed); - mastersTable->verticalHeader()->hide(); - - pluginsTable->setModel(mFilterProxyModel); - pluginsTable->setObjectName("PluginsTable"); - pluginsTable->setContextMenuPolicy(Qt::CustomContextMenu); - pluginsTable->setSortingEnabled(false); - pluginsTable->setSelectionBehavior(QAbstractItemView::SelectRows); - pluginsTable->setSelectionMode(QAbstractItemView::ExtendedSelection); - pluginsTable->setEditTriggers(QAbstractItemView::NoEditTriggers); - pluginsTable->setAlternatingRowColors(true); - pluginsTable->setVerticalScrollMode(QAbstractItemView::ScrollPerItem); - pluginsTable->horizontalHeader()->setStretchLastSection(true); - -// pluginsTable->verticalHeader()->setDefaultSectionSize(height); -// pluginsTable->verticalHeader()->setResizeMode(QHeaderView::Fixed); - - */ // Hide the profile elements profileLabel->hide(); profilesComboBox->hide(); @@ -107,7 +58,6 @@ FileDialog::FileDialog(QWidget *parent) : resize(600, 400); - // // connect(mDataFilesModel, SIGNAL(checkedItemsChanged(QStringList)), this, SLOT(updateOpenButton(QStringList))); //connect(mNameLineEdit, SIGNAL(textChanged(QString)), this, SLOT(updateCreateButton(QString))); diff --git a/apps/opencs/view/doc/filedialog.hpp b/apps/opencs/view/doc/filedialog.hpp index 2f4d4e381d..7944b7fb3d 100644 --- a/apps/opencs/view/doc/filedialog.hpp +++ b/apps/opencs/view/doc/filedialog.hpp @@ -53,12 +53,6 @@ private: QPushButton *mCreateButton; QDialogButtonBox *mButtonBox; - - DataFilesModel *mDataFilesModel; - - PluginsProxyModel *mPluginsProxyModel; - QSortFilterProxyModel *mMastersProxyModel; - QSortFilterProxyModel *mFilterProxyModel; }; #endif // FILEDIALOG_HPP diff --git a/components/fileorderlist/contentselector.cpp b/components/fileorderlist/contentselector.cpp index 16e0718910..6f8a86a490 100644 --- a/components/fileorderlist/contentselector.cpp +++ b/components/fileorderlist/contentselector.cpp @@ -9,6 +9,8 @@ FileOrderList::ContentSelector::ContentSelector(QWidget *parent) : QWidget(parent) { + setupUi(this); + buildModelsAndViews(); } void FileOrderList::ContentSelector::buildModelsAndViews() @@ -19,11 +21,6 @@ void FileOrderList::ContentSelector::buildModelsAndViews() mMasterProxyModel = new FileOrderList::MasterProxyModel (this, mDataFilesModel); mPluginsProxyModel = new PluginsProxyModel (this, mDataFilesModel); - - mFilterProxyModel = new QSortFilterProxyModel(); - mFilterProxyModel->setDynamicSortFilter(true); - mFilterProxyModel->setSourceModel(mPluginsProxyModel); - masterView->setModel(mMasterProxyModel); /* mastersTable->setModel(mMastersProxyModel); @@ -41,7 +38,7 @@ void FileOrderList::ContentSelector::buildModelsAndViews() mastersTable->verticalHeader()->setResizeMode(QHeaderView::Fixed); mastersTable->verticalHeader()->hide(); */ - pluginsTable->setModel(mFilterProxyModel); + pluginsTable->setModel(mPluginsProxyModel); pluginsTable->setObjectName("PluginsTable"); pluginsTable->setContextMenuPolicy(Qt::CustomContextMenu); pluginsTable->setSortingEnabled(false); @@ -79,8 +76,7 @@ void FileOrderList::ContentSelector::setCheckState(QModelIndex index) if (object->objectName() == QLatin1String("PluginsTable")) { - QModelIndex sourceIndex = mPluginsProxyModel->mapToSource( - mFilterProxyModel->mapToSource(index)); + QModelIndex sourceIndex = mPluginsProxyModel->mapToSource(index); if (sourceIndex.isValid()) { (mDataFilesModel->checkState(sourceIndex) == Qt::Checked) @@ -88,7 +84,7 @@ void FileOrderList::ContentSelector::setCheckState(QModelIndex index) : mDataFilesModel->setCheckState(sourceIndex, Qt::Checked); } } -/* + if (object->objectName() == QLatin1String("MastersTable")) { QModelIndex sourceIndex = mMasterProxyModel->mapToSource(index); @@ -98,7 +94,7 @@ void FileOrderList::ContentSelector::setCheckState(QModelIndex index) : mDataFilesModel->setCheckState(sourceIndex, Qt::Checked); } } -*/ + return; } @@ -110,16 +106,6 @@ QStringList FileOrderList::ContentSelector::checkedItemsPaths() void FileOrderList::ContentSelector::updateViews() { // Ensure the columns are hidden because sort() re-enables them - /* - mastersTable->setColumnHidden(1, true); - mastersTable->setColumnHidden(3, true); - mastersTable->setColumnHidden(4, true); - mastersTable->setColumnHidden(5, true); - mastersTable->setColumnHidden(6, true); - mastersTable->setColumnHidden(7, true); - mastersTable->setColumnHidden(8, true); - mastersTable->resizeColumnsToContents(); -*/ pluginsTable->setColumnHidden(1, true); pluginsTable->setColumnHidden(3, true); pluginsTable->setColumnHidden(4, true); diff --git a/components/fileorderlist/contentselector.hpp b/components/fileorderlist/contentselector.hpp index f17e3a9090..914e8bacb7 100644 --- a/components/fileorderlist/contentselector.hpp +++ b/components/fileorderlist/contentselector.hpp @@ -17,10 +17,11 @@ namespace FileOrderList { Q_OBJECT + protected: + DataFilesModel *mDataFilesModel; MasterProxyModel *mMasterProxyModel; PluginsProxyModel *mPluginsProxyModel; - QSortFilterProxyModel *mFilterProxyModel; public: explicit ContentSelector(QWidget *parent = 0); diff --git a/components/fileorderlist/model/pluginsproxymodel.cpp b/components/fileorderlist/model/pluginsproxymodel.cpp index f6864d85cd..9e3cdd7309 100644 --- a/components/fileorderlist/model/pluginsproxymodel.cpp +++ b/components/fileorderlist/model/pluginsproxymodel.cpp @@ -4,8 +4,9 @@ PluginsProxyModel::PluginsProxyModel(QObject *parent, DataFilesModel *model) : QSortFilterProxyModel(parent), mSourceModel (model) { - setFilterRegExp(QString("addon")); + setFilterRegExp (QString("addon")); setFilterRole (Qt::UserRole); + setDynamicSortFilter (true); if (model) setSourceModel (model); From 2878f51cd324c0dcc605b54f00b927a3dafac31c Mon Sep 17 00:00:00 2001 From: graffy76 Date: Sat, 17 Aug 2013 19:40:28 -0500 Subject: [PATCH 011/434] Reimplemented dependency selection feature Moved more code to ContentSelector Added support for omwgame and omwaddon files --- apps/launcher/datafilespage.cpp | 125 +++--------------- apps/launcher/datafilespage.hpp | 8 +- apps/opencs/view/doc/filedialog.cpp | 19 --- apps/opencs/view/doc/filedialog.hpp | 3 - components/fileorderlist/contentselector.cpp | 99 ++++++-------- components/fileorderlist/contentselector.hpp | 7 +- .../fileorderlist/model/datafilesmodel.cpp | 15 +-- .../fileorderlist/model/pluginsproxymodel.cpp | 4 +- .../fileorderlist/model/pluginsproxymodel.hpp | 2 - files/ui/datafilespage.ui | 44 +++--- 10 files changed, 99 insertions(+), 227 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index d077c3e0e9..5cbcae5459 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -29,29 +29,14 @@ DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gam , mLauncherSettings(launcherSettings) , ContentSelector(parent) { - // Adjust the tableview widths inside the splitter - QList sizeList; - sizeList << mLauncherSettings.value(QString("General/MastersTable/width"), QString("200")).toInt(); - sizeList << mLauncherSettings.value(QString("General/PluginTable/width"), QString("340")).toInt(); - - splitter->setSizes(sizeList); // Create a dialog for the new profile name input mNewProfileDialog = new TextInputDialog(tr("New Profile"), tr("Profile name:"), this); - connect(profilesComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCurrentIndexChanged(int))); - connect(mNewProfileDialog->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(updateOkButton(QString))); - connect(pluginsTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(setCheckState(QModelIndex))); - //connect(mastersTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(setCheckState(QModelIndex))); - - connect(pluginsTable, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint))); - //connect(mastersTable, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint))); - - //connect(filterLineEdit, SIGNAL(textChanged(QString)), this, SLOT(filterChanged(QString))); - - connect(splitter, SIGNAL(splitterMoved(int,int)), this, SLOT(updateSplitter())); + //connect(pluginView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint))); + //connect(masterView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint))); createActions(); setupDataFiles(); @@ -193,49 +178,11 @@ void DataFilesPage::updateOkButton(const QString &text) : mNewProfileDialog->setOkButtonEnabled(false); } -void DataFilesPage::updateSplitter() -{ - // Sigh, update the saved splitter size in settings only when moved - // Since getting mSplitter->sizes() if page is hidden returns invalid values - QList sizes = splitter->sizes(); - - mLauncherSettings.setValue(QString("General/MastersTable/width"), QString::number(sizes.at(0))); - mLauncherSettings.setValue(QString("General/PluginsTable/width"), QString::number(sizes.at(1))); -} - -void DataFilesPage::updateViews() -{ - // Ensure the columns are hidden because sort() re-enables them - /* - mastersTable->setColumnHidden(1, true); - mastersTable->setColumnHidden(2, true); - mastersTable->setColumnHidden(3, true); - mastersTable->setColumnHidden(4, true); - mastersTable->setColumnHidden(5, true); - mastersTable->setColumnHidden(6, true); - mastersTable->setColumnHidden(7, true); - mastersTable->setColumnHidden(8, true); -*/ - pluginsTable->setColumnHidden(1, true); - pluginsTable->setColumnHidden(2, true); - pluginsTable->setColumnHidden(3, true); - pluginsTable->setColumnHidden(4, true); - pluginsTable->setColumnHidden(5, true); - pluginsTable->setColumnHidden(6, true); - pluginsTable->setColumnHidden(7, true); - pluginsTable->setColumnHidden(8, true); -} - void DataFilesPage::setProfilesComboBoxIndex(int index) { profilesComboBox->setCurrentIndex(index); } -void DataFilesPage::slotCurrentIndexChanged(int index) -{ - emit profileChanged(index); -} - QAbstractItemModel* DataFilesPage::profilesComboBoxModel() { return profilesComboBox->model(); @@ -282,54 +229,13 @@ void DataFilesPage::on_deleteProfileAction_triggered() } } -void DataFilesPage::on_checkAction_triggered() -{ - if (pluginsTable->hasFocus()) - setPluginsCheckstates(Qt::Checked); - - //if (mastersTable->hasFocus()) - // setMastersCheckstates(Qt::Checked); - -} - -void DataFilesPage::on_uncheckAction_triggered() -{ - if (pluginsTable->hasFocus()) - setPluginsCheckstates(Qt::Unchecked); - - //if (mastersTable->hasFocus()) - // setMastersCheckstates(Qt::Unchecked); -} - -void DataFilesPage::setMastersCheckstates(Qt::CheckState state) -{/* - //if (!mastersTable->selectionModel()->hasSelection()) { - // return; - //} - - //QModelIndexList indexes = mastersTable->selectionModel()->selectedIndexes(); - - foreach (const QModelIndex &index, indexes) - { - if (!index.isValid()) - return; - - QModelIndex sourceIndex = mMastersProxyModel->mapToSource(index); - - if (!sourceIndex.isValid()) - return; - - mDataFilesModel->setCheckState(sourceIndex, state); - }*/ -} - void DataFilesPage::setPluginsCheckstates(Qt::CheckState state) { - if (!pluginsTable->selectionModel()->hasSelection()) { + if (!pluginView->selectionModel()->hasSelection()) { return; } - QModelIndexList indexes = pluginsTable->selectionModel()->selectedIndexes(); + QModelIndexList indexes = pluginView->selectionModel()->selectedIndexes(); foreach (const QModelIndex &index, indexes) { @@ -389,7 +295,7 @@ void DataFilesPage::profileRenamed(const QString &previous, const QString &curre loadSettings(); } - +/* void DataFilesPage::showContextMenu(const QPoint &point) { QObject *object = QObject::sender(); @@ -398,12 +304,12 @@ void DataFilesPage::showContextMenu(const QPoint &point) if (!object) return; - if (object->objectName() == QLatin1String("PluginsTable")) { - if (!pluginsTable->selectionModel()->hasSelection()) + if (object->objectName() == QLatin1String("PluginView")) { + if (!pluginView->selectionModel()->hasSelection()) return; - QPoint globalPos = pluginsTable->mapToGlobal(point); - QModelIndexList indexes = pluginsTable->selectionModel()->selectedIndexes(); + QPoint globalPos = pluginView->mapToGlobal(point); + QModelIndexList indexes = pluginView->selectionModel()->selectedIndexes(); // Show the check/uncheck actions depending on the state of the selected items uncheckAction->setEnabled(false); @@ -427,13 +333,13 @@ void DataFilesPage::showContextMenu(const QPoint &point) // Show menu mContextMenu->exec(globalPos); } -/* - if (object->objectName() == QLatin1String("MastersTable")) { - if (!mastersTable->selectionModel()->hasSelection()) + + if (object->objectName() == QLatin1String("MasterView")) { + if (!masterView->selectionModel()->hasSelection()) return; - QPoint globalPos = mastersTable->mapToGlobal(point); - QModelIndexList indexes = mastersTable->selectionModel()->selectedIndexes(); + QPoint globalPos = masterView->mapToGlobal(point); + QModelIndexList indexes = masterView->selectionModel()->selectedIndexes(); // Show the check/uncheck actions depending on the state of the selected items uncheckAction->setEnabled(false); @@ -456,5 +362,6 @@ void DataFilesPage::showContextMenu(const QPoint &point) mContextMenu->exec(globalPos); } - */ + } +*/ diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index 1c528ab26e..db391519f4 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -38,21 +38,16 @@ signals: public slots: void setProfilesComboBoxIndex(int index); - void showContextMenu(const QPoint &point); + //void showContextMenu(const QPoint &point); void profileChanged(const QString &previous, const QString ¤t); void profileRenamed(const QString &previous, const QString ¤t); void updateOkButton(const QString &text); - void updateSplitter(); - void updateViews(); // Action slots void on_newProfileAction_triggered(); void on_deleteProfileAction_triggered(); - void on_checkAction_triggered(); - void on_uncheckAction_triggered(); private slots: - void slotCurrentIndexChanged(int index); private: @@ -65,7 +60,6 @@ private: TextInputDialog *mNewProfileDialog; - void setMastersCheckstates(Qt::CheckState state); void setPluginsCheckstates(Qt::CheckState state); void createActions(); diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index 2f3a6dc219..9f1c72068e 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -49,23 +49,11 @@ FileDialog::FileDialog(QWidget *parent) : verticalLayout->addLayout(nameLayout); verticalLayout->addWidget(mButtonBox); - // Set sizes - QList sizeList; - sizeList << 175; - sizeList << 200; - - splitter->setSizes(sizeList); - resize(600, 400); // connect(mDataFilesModel, SIGNAL(checkedItemsChanged(QStringList)), this, SLOT(updateOpenButton(QStringList))); //connect(mNameLineEdit, SIGNAL(textChanged(QString)), this, SLOT(updateCreateButton(QString))); - //connect(filterLineEdit, SIGNAL(textChanged(QString)), this, SLOT(filterChanged(QString))); - - // connect(pluginsTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(setCheckState(QModelIndex))); - //connect(mastersTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(setCheckState(QModelIndex))); - // connect(mCreateButton, SIGNAL(clicked()), this, SLOT(createButtonClicked())); // connect(mButtonBox, SIGNAL(accepted()), this, SLOT(accept())); @@ -89,13 +77,6 @@ void FileDialog::updateCreateButton(const QString &name) mCreateButton->setEnabled(!name.isEmpty()); } -/* -void FileDialog::filterChanged(const QString &filter) -{ - QRegExp filterRe(filter, Qt::CaseInsensitive, QRegExp::FixedString); - mFilterProxyModel->setFilterRegExp(filterRe); -} -*/ QString FileDialog::fileName() { diff --git a/apps/opencs/view/doc/filedialog.hpp b/apps/opencs/view/doc/filedialog.hpp index 7944b7fb3d..232f250beb 100644 --- a/apps/opencs/view/doc/filedialog.hpp +++ b/apps/opencs/view/doc/filedialog.hpp @@ -42,9 +42,6 @@ private slots: void updateOpenButton(const QStringList &items); void updateCreateButton(const QString &name); - - //void filterChanged(const QString &filter); - void createButtonClicked(); private: diff --git a/components/fileorderlist/contentselector.cpp b/components/fileorderlist/contentselector.cpp index 6f8a86a490..e7ab9b0cfc 100644 --- a/components/fileorderlist/contentselector.cpp +++ b/components/fileorderlist/contentselector.cpp @@ -6,6 +6,7 @@ #include +#include FileOrderList::ContentSelector::ContentSelector(QWidget *parent) : QWidget(parent) { @@ -22,40 +23,20 @@ void FileOrderList::ContentSelector::buildModelsAndViews() mPluginsProxyModel = new PluginsProxyModel (this, mDataFilesModel); masterView->setModel(mMasterProxyModel); -/* - mastersTable->setModel(mMastersProxyModel); - mastersTable->setObjectName("MastersTable"); - mastersTable->setContextMenuPolicy(Qt::CustomContextMenu); - mastersTable->setSortingEnabled(false); - mastersTable->setSelectionBehavior(QAbstractItemView::SelectRows); - mastersTable->setSelectionMode(QAbstractItemView::ExtendedSelection); - mastersTable->setEditTriggers(QAbstractItemView::NoEditTriggers); - mastersTable->setAlternatingRowColors(true); - mastersTable->horizontalHeader()->setStretchLastSection(true); - - // Set the row height to the size of the checkboxes - mastersTable->verticalHeader()->setDefaultSectionSize(height); - mastersTable->verticalHeader()->setResizeMode(QHeaderView::Fixed); - mastersTable->verticalHeader()->hide(); -*/ - pluginsTable->setModel(mPluginsProxyModel); - pluginsTable->setObjectName("PluginsTable"); - pluginsTable->setContextMenuPolicy(Qt::CustomContextMenu); - pluginsTable->setSortingEnabled(false); - pluginsTable->setSelectionBehavior(QAbstractItemView::SelectRows); - pluginsTable->setSelectionMode(QAbstractItemView::ExtendedSelection); - pluginsTable->setEditTriggers(QAbstractItemView::NoEditTriggers); - pluginsTable->setAlternatingRowColors(true); - pluginsTable->setVerticalScrollMode(QAbstractItemView::ScrollPerItem); - pluginsTable->horizontalHeader()->setStretchLastSection(true); + pluginView->setModel(mPluginsProxyModel); connect(mDataFilesModel, SIGNAL(layoutChanged()), this, SLOT(updateViews())); + connect(masterView, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCurrentMasterIndexChanged(int))); + + connect(profilesComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCurrentProfileIndexChanged(int))); } void FileOrderList::ContentSelector::addFiles(const QString &path) { mDataFilesModel->addFiles(path); mDataFilesModel->sort(3); // Sort by date accessed + masterView->setCurrentIndex(-1); + mDataFilesModel->uncheckAll(); } void FileOrderList::ContentSelector::setEncoding(const QString &encoding) @@ -63,39 +44,22 @@ void FileOrderList::ContentSelector::setEncoding(const QString &encoding) mDataFilesModel->setEncoding(encoding); } -void FileOrderList::ContentSelector::setCheckState(QModelIndex index) +void FileOrderList::ContentSelector::setCheckState(QModelIndex index, QSortFilterProxyModel *model) { if (!index.isValid()) return; - QObject *object = QObject::sender(); - - // Not a signal-slot call - if (!object) + if (!model) return; + QModelIndex sourceIndex = model->mapToSource(index); - if (object->objectName() == QLatin1String("PluginsTable")) { - QModelIndex sourceIndex = mPluginsProxyModel->mapToSource(index); - - if (sourceIndex.isValid()) { - (mDataFilesModel->checkState(sourceIndex) == Qt::Checked) - ? mDataFilesModel->setCheckState(sourceIndex, Qt::Unchecked) - : mDataFilesModel->setCheckState(sourceIndex, Qt::Checked); - } + if (sourceIndex.isValid()) + { + (mDataFilesModel->checkState(sourceIndex) == Qt::Checked) + ? mDataFilesModel->setCheckState(sourceIndex, Qt::Unchecked) + : mDataFilesModel->setCheckState(sourceIndex, Qt::Checked); } - - if (object->objectName() == QLatin1String("MastersTable")) { - QModelIndex sourceIndex = mMasterProxyModel->mapToSource(index); - - if (sourceIndex.isValid()) { - (mDataFilesModel->checkState(sourceIndex) == Qt::Checked) - ? mDataFilesModel->setCheckState(sourceIndex, Qt::Unchecked) - : mDataFilesModel->setCheckState(sourceIndex, Qt::Checked); - } - } - - return; } QStringList FileOrderList::ContentSelector::checkedItemsPaths() @@ -106,13 +70,30 @@ QStringList FileOrderList::ContentSelector::checkedItemsPaths() void FileOrderList::ContentSelector::updateViews() { // Ensure the columns are hidden because sort() re-enables them - pluginsTable->setColumnHidden(1, true); - pluginsTable->setColumnHidden(3, true); - pluginsTable->setColumnHidden(4, true); - pluginsTable->setColumnHidden(5, true); - pluginsTable->setColumnHidden(6, true); - pluginsTable->setColumnHidden(7, true); - pluginsTable->setColumnHidden(8, true); - pluginsTable->resizeColumnsToContents(); + pluginView->setColumnHidden(1, true); + pluginView->setColumnHidden(3, true); + pluginView->setColumnHidden(4, true); + pluginView->setColumnHidden(5, true); + pluginView->setColumnHidden(6, true); + pluginView->setColumnHidden(7, true); + pluginView->setColumnHidden(8, true); + pluginView->resizeColumnsToContents(); } + +void FileOrderList::ContentSelector::slotCurrentProfileIndexChanged(int index) +{ + emit profileChanged(index); +} + +void FileOrderList::ContentSelector::slotCurrentMasterIndexChanged(int index) +{ + qDebug() << "index Changed: " << index; + QObject *object = QObject::sender(); + + // Not a signal-slot call + if (!object) + return; + + setCheckState(mMasterProxyModel->index(index, 0), mMasterProxyModel); +} diff --git a/components/fileorderlist/contentselector.hpp b/components/fileorderlist/contentselector.hpp index 914e8bacb7..138ffc6298 100644 --- a/components/fileorderlist/contentselector.hpp +++ b/components/fileorderlist/contentselector.hpp @@ -30,11 +30,16 @@ namespace FileOrderList void addFiles(const QString &path); void setEncoding(const QString &encoding); - void setCheckState(QModelIndex index); + void setCheckState(QModelIndex index, QSortFilterProxyModel *model); QStringList checkedItemsPaths(); + signals: + void profileChanged(int index); + private slots: void updateViews(); + void slotCurrentProfileIndexChanged(int index); + void slotCurrentMasterIndexChanged(int index); }; } diff --git a/components/fileorderlist/model/datafilesmodel.cpp b/components/fileorderlist/model/datafilesmodel.cpp index 3f63e73cce..8e27f1f751 100644 --- a/components/fileorderlist/model/datafilesmodel.cpp +++ b/components/fileorderlist/model/datafilesmodel.cpp @@ -174,7 +174,7 @@ Qt::ItemFlags DataFilesModel::flags(const QModelIndex &index) const if (index.column() == 0) { return Qt::ItemIsUserCheckable | Qt::ItemIsSelectable; } else { - return Qt::NoItemFlags | Qt::ItemIsSelectable; + return Qt::ItemIsSelectable; } } @@ -270,7 +270,7 @@ void DataFilesModel::addFiles(const QString &path) { QDir dir(path); QStringList filters; - filters << "*.esp" << "*.esm"; + filters << "*.esp" << "*.esm" << "*.omwgame" << "*.omwaddon"; dir.setNameFilters(filters); // Create a decoder for non-latin characters in esx metadata @@ -319,9 +319,10 @@ void DataFilesModel::addFiles(const QString &path) // Put the file in the table if (findItem(path) == 0) addFile(file); + } catch(std::runtime_error &e) { // An error occurred while reading the .esp - qWarning() << "Error reading esp: " << e.what(); + qWarning() << "Error reading addon file: " << e.what(); continue; } @@ -436,14 +437,10 @@ QStringList DataFilesModel::uncheckedItems() bool DataFilesModel::canBeChecked(EsmFile *file) const { //element can be checked if all its dependencies are - bool canBeChecked = true; foreach (const QString &master, file->masters()) { if (!mCheckStates.contains(master) || mCheckStates[master] != Qt::Checked) - { - canBeChecked = false; - break; - } + return false; } - return canBeChecked; + return true; } diff --git a/components/fileorderlist/model/pluginsproxymodel.cpp b/components/fileorderlist/model/pluginsproxymodel.cpp index 9e3cdd7309..18aebc6b68 100644 --- a/components/fileorderlist/model/pluginsproxymodel.cpp +++ b/components/fileorderlist/model/pluginsproxymodel.cpp @@ -2,7 +2,7 @@ #include "datafilesmodel.hpp" PluginsProxyModel::PluginsProxyModel(QObject *parent, DataFilesModel *model) : - QSortFilterProxyModel(parent), mSourceModel (model) + QSortFilterProxyModel(parent) { setFilterRegExp (QString("addon")); setFilterRole (Qt::UserRole); @@ -25,7 +25,7 @@ QVariant PluginsProxyModel::data(const QModelIndex &index, int role) const if (index.column() != 0) return QVariant(); - return mSourceModel->checkState(index); + return static_cast(sourceModel())->checkState(mapToSource(index)); } }; diff --git a/components/fileorderlist/model/pluginsproxymodel.hpp b/components/fileorderlist/model/pluginsproxymodel.hpp index e148ea3b16..cfade092eb 100644 --- a/components/fileorderlist/model/pluginsproxymodel.hpp +++ b/components/fileorderlist/model/pluginsproxymodel.hpp @@ -11,8 +11,6 @@ class PluginsProxyModel : public QSortFilterProxyModel { Q_OBJECT - DataFilesModel *mSourceModel; - public: explicit PluginsProxyModel(QObject *parent = 0, DataFilesModel *model = 0); diff --git a/files/ui/datafilespage.ui b/files/ui/datafilespage.ui index 816d288a65..87213c5cc8 100644 --- a/files/ui/datafilespage.ui +++ b/files/ui/datafilespage.ui @@ -10,7 +10,7 @@ 304 - + @@ -23,21 +23,33 @@ - - - - 0 - 0 - - - - Qt::Horizontal - - - false - - - + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectItems + + + Qt::ElideLeft + + + false + + + false + + + false + + + + From a9db983233dde491c0b90b53ca427e4427d0188f Mon Sep 17 00:00:00 2001 From: graffy76 Date: Sun, 18 Aug 2013 07:29:48 -0500 Subject: [PATCH 012/434] Fixing row-click selection --- components/fileorderlist/contentselector.cpp | 2 +- files/ui/datafilespage.ui | 24 ++++++++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/components/fileorderlist/contentselector.cpp b/components/fileorderlist/contentselector.cpp index e7ab9b0cfc..6254601dc6 100644 --- a/components/fileorderlist/contentselector.cpp +++ b/components/fileorderlist/contentselector.cpp @@ -26,8 +26,8 @@ void FileOrderList::ContentSelector::buildModelsAndViews() pluginView->setModel(mPluginsProxyModel); connect(mDataFilesModel, SIGNAL(layoutChanged()), this, SLOT(updateViews())); + //connect(pluginView, SIGNAL()) connect(masterView, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCurrentMasterIndexChanged(int))); - connect(profilesComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCurrentProfileIndexChanged(int))); } diff --git a/files/ui/datafilespage.ui b/files/ui/datafilespage.ui index 87213c5cc8..cf6f30b998 100644 --- a/files/ui/datafilespage.ui +++ b/files/ui/datafilespage.ui @@ -29,11 +29,14 @@ QAbstractItemView::NoEditTriggers + + true + QAbstractItemView::SingleSelection - QAbstractItemView::SelectItems + QAbstractItemView::SelectRows Qt::ElideLeft @@ -153,5 +156,22 @@ - + + + pluginView + clicked(QModelIndex) + checkAction + toggle() + + + 258 + 151 + + + -1 + -1 + + + + From 66e50343adba0bfca059da804dee0b865eba84d9 Mon Sep 17 00:00:00 2001 From: graffy76 Date: Sun, 18 Aug 2013 08:54:51 -0500 Subject: [PATCH 013/434] Fixed row-selection/check feature --- components/fileorderlist/contentselector.cpp | 11 ++++++-- components/fileorderlist/contentselector.hpp | 3 ++ .../fileorderlist/model/datafilesmodel.cpp | 27 +++++++++--------- files/ui/datafilespage.ui | 28 +++++++------------ 4 files changed, 35 insertions(+), 34 deletions(-) diff --git a/components/fileorderlist/contentselector.cpp b/components/fileorderlist/contentselector.cpp index 6254601dc6..b2a5c5ba59 100644 --- a/components/fileorderlist/contentselector.cpp +++ b/components/fileorderlist/contentselector.cpp @@ -7,6 +7,9 @@ #include #include +#include +#include + FileOrderList::ContentSelector::ContentSelector(QWidget *parent) : QWidget(parent) { @@ -26,7 +29,7 @@ void FileOrderList::ContentSelector::buildModelsAndViews() pluginView->setModel(mPluginsProxyModel); connect(mDataFilesModel, SIGNAL(layoutChanged()), this, SLOT(updateViews())); - //connect(pluginView, SIGNAL()) + connect(pluginView, SIGNAL(clicked(const QModelIndex &)), this, SLOT(slotPluginTableItemClicked(const QModelIndex &))); connect(masterView, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCurrentMasterIndexChanged(int))); connect(profilesComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCurrentProfileIndexChanged(int))); } @@ -88,7 +91,6 @@ void FileOrderList::ContentSelector::slotCurrentProfileIndexChanged(int index) void FileOrderList::ContentSelector::slotCurrentMasterIndexChanged(int index) { - qDebug() << "index Changed: " << index; QObject *object = QObject::sender(); // Not a signal-slot call @@ -97,3 +99,8 @@ void FileOrderList::ContentSelector::slotCurrentMasterIndexChanged(int index) setCheckState(mMasterProxyModel->index(index, 0), mMasterProxyModel); } + +void FileOrderList::ContentSelector::slotPluginTableItemClicked(const QModelIndex &index) +{ + setCheckState(index, mPluginsProxyModel); +} diff --git a/components/fileorderlist/contentselector.hpp b/components/fileorderlist/contentselector.hpp index 138ffc6298..f1d8f6927a 100644 --- a/components/fileorderlist/contentselector.hpp +++ b/components/fileorderlist/contentselector.hpp @@ -30,8 +30,10 @@ namespace FileOrderList void addFiles(const QString &path); void setEncoding(const QString &encoding); + void setPluginCheckState(); void setCheckState(QModelIndex index, QSortFilterProxyModel *model); QStringList checkedItemsPaths(); + void on_checkAction_triggered(); signals: void profileChanged(int index); @@ -40,6 +42,7 @@ namespace FileOrderList void updateViews(); void slotCurrentProfileIndexChanged(int index); void slotCurrentMasterIndexChanged(int index); + void slotPluginTableItemClicked(const QModelIndex &index); }; } diff --git a/components/fileorderlist/model/datafilesmodel.cpp b/components/fileorderlist/model/datafilesmodel.cpp index 8e27f1f751..ae842381e7 100644 --- a/components/fileorderlist/model/datafilesmodel.cpp +++ b/components/fileorderlist/model/datafilesmodel.cpp @@ -30,7 +30,19 @@ void DataFilesModel::setEncoding(const QString &encoding) void DataFilesModel::setCheckState(const QModelIndex &index, Qt::CheckState state) { - setData(index, state, Qt::CheckStateRole); + if (!index.isValid()) + return; + + QString name = item(index.row())->fileName(); + mCheckStates[name] = state; + + // Force a redraw of the view since unchecking one item can affect another + QModelIndex firstIndex = indexFromItem(mFiles.first()); + QModelIndex lastIndex = indexFromItem(mFiles.last()); + + emit dataChanged(firstIndex, lastIndex); + emit checkedItemsChanged(checkedItems()); + } Qt::CheckState DataFilesModel::checkState(const QModelIndex &index) @@ -210,19 +222,6 @@ bool DataFilesModel::setData(const QModelIndex &index, const QVariant &value, in if (!index.isValid()) return false; - if (role == Qt::CheckStateRole) { - QString name = item(index.row())->fileName(); - mCheckStates[name] = static_cast(value.toInt()); - - // Force a redraw of the view since unchecking one item can affect another - QModelIndex firstIndex = indexFromItem(mFiles.first()); - QModelIndex lastIndex = indexFromItem(mFiles.last()); - - emit dataChanged(firstIndex, lastIndex); - emit checkedItemsChanged(checkedItems()); - return true; - } - return false; } diff --git a/files/ui/datafilespage.ui b/files/ui/datafilespage.ui index cf6f30b998..a6c1768b5d 100644 --- a/files/ui/datafilespage.ui +++ b/files/ui/datafilespage.ui @@ -10,6 +10,9 @@ 304 + + Qt::DefaultContextMenu + @@ -26,6 +29,9 @@ + + Qt::DefaultContextMenu + QAbstractItemView::NoEditTriggers @@ -138,6 +144,9 @@ + + true + Check Selection @@ -156,22 +165,5 @@ - - - pluginView - clicked(QModelIndex) - checkAction - toggle() - - - 258 - 151 - - - -1 - -1 - - - - + From 45277c00825c759bb826a25e0e73cf940968086f Mon Sep 17 00:00:00 2001 From: graffy76 Date: Sun, 18 Aug 2013 09:34:33 -0500 Subject: [PATCH 014/434] Minor changes Stretched table columns to fit widget width Reduced width of opencs file dialog Hid the file size column for launcher Added alternating row colors in table view --- apps/launcher/datafilespage.cpp | 1 + apps/opencs/view/doc/filedialog.cpp | 2 +- components/fileorderlist/contentselector.cpp | 1 + files/ui/datafilespage.ui | 9 +++++++++ 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 5cbcae5459..44f92d7fe0 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -30,6 +30,7 @@ DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gam , ContentSelector(parent) { + pluginView->hideColumn(2); // Create a dialog for the new profile name input mNewProfileDialog = new TextInputDialog(tr("New Profile"), tr("Profile name:"), this); diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index 9f1c72068e..252c760faa 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -49,7 +49,7 @@ FileDialog::FileDialog(QWidget *parent) : verticalLayout->addLayout(nameLayout); verticalLayout->addWidget(mButtonBox); - resize(600, 400); + resize(400, 400); // connect(mDataFilesModel, SIGNAL(checkedItemsChanged(QStringList)), this, SLOT(updateOpenButton(QStringList))); //connect(mNameLineEdit, SIGNAL(textChanged(QString)), this, SLOT(updateCreateButton(QString))); diff --git a/components/fileorderlist/contentselector.cpp b/components/fileorderlist/contentselector.cpp index b2a5c5ba59..27be996fc8 100644 --- a/components/fileorderlist/contentselector.cpp +++ b/components/fileorderlist/contentselector.cpp @@ -27,6 +27,7 @@ void FileOrderList::ContentSelector::buildModelsAndViews() masterView->setModel(mMasterProxyModel); pluginView->setModel(mPluginsProxyModel); + pluginView-> connect(mDataFilesModel, SIGNAL(layoutChanged()), this, SLOT(updateViews())); connect(pluginView, SIGNAL(clicked(const QModelIndex &)), this, SLOT(slotPluginTableItemClicked(const QModelIndex &))); diff --git a/files/ui/datafilespage.ui b/files/ui/datafilespage.ui index a6c1768b5d..07ad9d3ba6 100644 --- a/files/ui/datafilespage.ui +++ b/files/ui/datafilespage.ui @@ -29,6 +29,12 @@ + + + 0 + 0 + + Qt::DefaultContextMenu @@ -53,6 +59,9 @@ false + + true + false From d0363b037cc9716ceb5f7d03594a1a836e8724c1 Mon Sep 17 00:00:00 2001 From: graffy76 Date: Sun, 18 Aug 2013 09:41:02 -0500 Subject: [PATCH 015/434] Renamed components/fileorderlist to components/esxselector --- components/CMakeLists.txt | 2 +- components/{fileorderlist => esxselector}/contentselector.cpp | 0 components/{fileorderlist => esxselector}/contentselector.hpp | 0 components/{fileorderlist => esxselector}/masterproxymodel.cpp | 0 components/{fileorderlist => esxselector}/masterproxymodel.hpp | 0 .../{fileorderlist => esxselector}/model/datafilesmodel.cpp | 0 .../{fileorderlist => esxselector}/model/datafilesmodel.hpp | 0 components/{fileorderlist => esxselector}/model/esm/esmfile.cpp | 0 components/{fileorderlist => esxselector}/model/esm/esmfile.hpp | 0 components/{fileorderlist => esxselector}/model/modelitem.cpp | 0 components/{fileorderlist => esxselector}/model/modelitem.hpp | 0 .../{fileorderlist => esxselector}/model/pluginsproxymodel.cpp | 0 .../{fileorderlist => esxselector}/model/pluginsproxymodel.hpp | 0 .../{fileorderlist => esxselector}/utils/comboboxlineedit.cpp | 0 .../{fileorderlist => esxselector}/utils/comboboxlineedit.hpp | 0 components/{fileorderlist => esxselector}/utils/lineedit.cpp | 0 components/{fileorderlist => esxselector}/utils/lineedit.hpp | 0 components/{fileorderlist => esxselector}/utils/naturalsort.cpp | 0 components/{fileorderlist => esxselector}/utils/naturalsort.hpp | 0 .../{fileorderlist => esxselector}/utils/profilescombobox.cpp | 0 .../{fileorderlist => esxselector}/utils/profilescombobox.hpp | 0 21 files changed, 1 insertion(+), 1 deletion(-) rename components/{fileorderlist => esxselector}/contentselector.cpp (100%) rename components/{fileorderlist => esxselector}/contentselector.hpp (100%) rename components/{fileorderlist => esxselector}/masterproxymodel.cpp (100%) rename components/{fileorderlist => esxselector}/masterproxymodel.hpp (100%) rename components/{fileorderlist => esxselector}/model/datafilesmodel.cpp (100%) rename components/{fileorderlist => esxselector}/model/datafilesmodel.hpp (100%) rename components/{fileorderlist => esxselector}/model/esm/esmfile.cpp (100%) rename components/{fileorderlist => esxselector}/model/esm/esmfile.hpp (100%) rename components/{fileorderlist => esxselector}/model/modelitem.cpp (100%) rename components/{fileorderlist => esxselector}/model/modelitem.hpp (100%) rename components/{fileorderlist => esxselector}/model/pluginsproxymodel.cpp (100%) rename components/{fileorderlist => esxselector}/model/pluginsproxymodel.hpp (100%) rename components/{fileorderlist => esxselector}/utils/comboboxlineedit.cpp (100%) rename components/{fileorderlist => esxselector}/utils/comboboxlineedit.hpp (100%) rename components/{fileorderlist => esxselector}/utils/lineedit.cpp (100%) rename components/{fileorderlist => esxselector}/utils/lineedit.hpp (100%) rename components/{fileorderlist => esxselector}/utils/naturalsort.cpp (100%) rename components/{fileorderlist => esxselector}/utils/naturalsort.hpp (100%) rename components/{fileorderlist => esxselector}/utils/profilescombobox.cpp (100%) rename components/{fileorderlist => esxselector}/utils/profilescombobox.hpp (100%) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 19af6c3b21..85f1a3508e 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -71,7 +71,7 @@ set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/datafilespage.ui find_package(Qt4 COMPONENTS QtCore QtGui) if(QT_QTGUI_LIBRARY AND QT_QTCORE_LIBRARY) - add_component_qt_dir (fileorderlist + add_component_qt_dir (esxselector masterproxymodel contentselector model/modelitem model/datafilesmodel model/pluginsproxymodel model/esm/esmfile utils/profilescombobox utils/comboboxlineedit utils/lineedit utils/naturalsort diff --git a/components/fileorderlist/contentselector.cpp b/components/esxselector/contentselector.cpp similarity index 100% rename from components/fileorderlist/contentselector.cpp rename to components/esxselector/contentselector.cpp diff --git a/components/fileorderlist/contentselector.hpp b/components/esxselector/contentselector.hpp similarity index 100% rename from components/fileorderlist/contentselector.hpp rename to components/esxselector/contentselector.hpp diff --git a/components/fileorderlist/masterproxymodel.cpp b/components/esxselector/masterproxymodel.cpp similarity index 100% rename from components/fileorderlist/masterproxymodel.cpp rename to components/esxselector/masterproxymodel.cpp diff --git a/components/fileorderlist/masterproxymodel.hpp b/components/esxselector/masterproxymodel.hpp similarity index 100% rename from components/fileorderlist/masterproxymodel.hpp rename to components/esxselector/masterproxymodel.hpp diff --git a/components/fileorderlist/model/datafilesmodel.cpp b/components/esxselector/model/datafilesmodel.cpp similarity index 100% rename from components/fileorderlist/model/datafilesmodel.cpp rename to components/esxselector/model/datafilesmodel.cpp diff --git a/components/fileorderlist/model/datafilesmodel.hpp b/components/esxselector/model/datafilesmodel.hpp similarity index 100% rename from components/fileorderlist/model/datafilesmodel.hpp rename to components/esxselector/model/datafilesmodel.hpp diff --git a/components/fileorderlist/model/esm/esmfile.cpp b/components/esxselector/model/esm/esmfile.cpp similarity index 100% rename from components/fileorderlist/model/esm/esmfile.cpp rename to components/esxselector/model/esm/esmfile.cpp diff --git a/components/fileorderlist/model/esm/esmfile.hpp b/components/esxselector/model/esm/esmfile.hpp similarity index 100% rename from components/fileorderlist/model/esm/esmfile.hpp rename to components/esxselector/model/esm/esmfile.hpp diff --git a/components/fileorderlist/model/modelitem.cpp b/components/esxselector/model/modelitem.cpp similarity index 100% rename from components/fileorderlist/model/modelitem.cpp rename to components/esxselector/model/modelitem.cpp diff --git a/components/fileorderlist/model/modelitem.hpp b/components/esxselector/model/modelitem.hpp similarity index 100% rename from components/fileorderlist/model/modelitem.hpp rename to components/esxselector/model/modelitem.hpp diff --git a/components/fileorderlist/model/pluginsproxymodel.cpp b/components/esxselector/model/pluginsproxymodel.cpp similarity index 100% rename from components/fileorderlist/model/pluginsproxymodel.cpp rename to components/esxselector/model/pluginsproxymodel.cpp diff --git a/components/fileorderlist/model/pluginsproxymodel.hpp b/components/esxselector/model/pluginsproxymodel.hpp similarity index 100% rename from components/fileorderlist/model/pluginsproxymodel.hpp rename to components/esxselector/model/pluginsproxymodel.hpp diff --git a/components/fileorderlist/utils/comboboxlineedit.cpp b/components/esxselector/utils/comboboxlineedit.cpp similarity index 100% rename from components/fileorderlist/utils/comboboxlineedit.cpp rename to components/esxselector/utils/comboboxlineedit.cpp diff --git a/components/fileorderlist/utils/comboboxlineedit.hpp b/components/esxselector/utils/comboboxlineedit.hpp similarity index 100% rename from components/fileorderlist/utils/comboboxlineedit.hpp rename to components/esxselector/utils/comboboxlineedit.hpp diff --git a/components/fileorderlist/utils/lineedit.cpp b/components/esxselector/utils/lineedit.cpp similarity index 100% rename from components/fileorderlist/utils/lineedit.cpp rename to components/esxselector/utils/lineedit.cpp diff --git a/components/fileorderlist/utils/lineedit.hpp b/components/esxselector/utils/lineedit.hpp similarity index 100% rename from components/fileorderlist/utils/lineedit.hpp rename to components/esxselector/utils/lineedit.hpp diff --git a/components/fileorderlist/utils/naturalsort.cpp b/components/esxselector/utils/naturalsort.cpp similarity index 100% rename from components/fileorderlist/utils/naturalsort.cpp rename to components/esxselector/utils/naturalsort.cpp diff --git a/components/fileorderlist/utils/naturalsort.hpp b/components/esxselector/utils/naturalsort.hpp similarity index 100% rename from components/fileorderlist/utils/naturalsort.hpp rename to components/esxselector/utils/naturalsort.hpp diff --git a/components/fileorderlist/utils/profilescombobox.cpp b/components/esxselector/utils/profilescombobox.cpp similarity index 100% rename from components/fileorderlist/utils/profilescombobox.cpp rename to components/esxselector/utils/profilescombobox.cpp diff --git a/components/fileorderlist/utils/profilescombobox.hpp b/components/esxselector/utils/profilescombobox.hpp similarity index 100% rename from components/fileorderlist/utils/profilescombobox.hpp rename to components/esxselector/utils/profilescombobox.hpp From a14e0b32d8daa1fad311d535a65bb14c5b123b91 Mon Sep 17 00:00:00 2001 From: graffy76 Date: Sun, 18 Aug 2013 09:48:13 -0500 Subject: [PATCH 016/434] Restructured esxselector directory Added ./view Removed ./utils and ./model/esm Relocated code accordingly. --- apps/launcher/datafilespage.hpp | 2 +- apps/opencs/view/doc/filedialog.hpp | 2 +- components/CMakeLists.txt | 7 +-- components/esxselector/model/esm/esmfile.cpp | 50 ----------------- components/esxselector/model/esm/esmfile.hpp | 54 ------------------- .../{ => model}/masterproxymodel.cpp | 4 +- .../{ => model}/masterproxymodel.hpp | 2 +- .../{utils => model}/naturalsort.cpp | 0 .../{utils => model}/naturalsort.hpp | 0 .../{utils => view}/comboboxlineedit.cpp | 0 .../{utils => view}/comboboxlineedit.hpp | 0 .../{ => view}/contentselector.cpp | 22 ++++---- .../{ => view}/contentselector.hpp | 0 .../esxselector/{utils => view}/lineedit.cpp | 0 .../esxselector/{utils => view}/lineedit.hpp | 0 .../{utils => view}/profilescombobox.cpp | 0 .../{utils => view}/profilescombobox.hpp | 0 17 files changed, 20 insertions(+), 123 deletions(-) delete mode 100644 components/esxselector/model/esm/esmfile.cpp delete mode 100644 components/esxselector/model/esm/esmfile.hpp rename components/esxselector/{ => model}/masterproxymodel.cpp (57%) rename components/esxselector/{ => model}/masterproxymodel.hpp (95%) rename components/esxselector/{utils => model}/naturalsort.cpp (100%) rename components/esxselector/{utils => model}/naturalsort.hpp (100%) rename components/esxselector/{utils => view}/comboboxlineedit.cpp (100%) rename components/esxselector/{utils => view}/comboboxlineedit.hpp (100%) rename components/esxselector/{ => view}/contentselector.cpp (74%) rename components/esxselector/{ => view}/contentselector.hpp (100%) rename components/esxselector/{utils => view}/lineedit.cpp (100%) rename components/esxselector/{utils => view}/lineedit.hpp (100%) rename components/esxselector/{utils => view}/profilescombobox.cpp (100%) rename components/esxselector/{utils => view}/profilescombobox.hpp (100%) diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index db391519f4..356cb88d63 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -20,7 +20,7 @@ class PluginsProxyModel; namespace Files { struct ConfigurationManager; } -class DataFilesPage : public FileOrderList::ContentSelector +class DataFilesPage : public EsxSelector::ContentSelector { Q_OBJECT public: diff --git a/apps/opencs/view/doc/filedialog.hpp b/apps/opencs/view/doc/filedialog.hpp index 232f250beb..1b4d09745c 100644 --- a/apps/opencs/view/doc/filedialog.hpp +++ b/apps/opencs/view/doc/filedialog.hpp @@ -18,7 +18,7 @@ class QMenu; class DataFilesModel; class PluginsProxyModel; -class FileDialog : public FileOrderList::ContentSelector +class FileDialog : public EsxSelector::ContentSelector { Q_OBJECT public: diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 85f1a3508e..4f14fa9d96 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -72,9 +72,10 @@ find_package(Qt4 COMPONENTS QtCore QtGui) if(QT_QTGUI_LIBRARY AND QT_QTCORE_LIBRARY) add_component_qt_dir (esxselector - masterproxymodel contentselector - model/modelitem model/datafilesmodel model/pluginsproxymodel model/esm/esmfile - utils/profilescombobox utils/comboboxlineedit utils/lineedit utils/naturalsort + model/masterproxymodel model/modelitem model/datafilesmodel + model/pluginsproxymodel model/esm/esmfile model/naturalsort + view/profilescombobox view/comboboxlineedit + view/lineedit view/contentselector ) include(${QT_USE_FILE}) diff --git a/components/esxselector/model/esm/esmfile.cpp b/components/esxselector/model/esm/esmfile.cpp deleted file mode 100644 index 93d83091e7..0000000000 --- a/components/esxselector/model/esm/esmfile.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include "esmfile.hpp" - -EsmFile::EsmFile(QString fileName, ModelItem *parent) - : ModelItem(parent) -{ - mFileName = fileName; - mSize = 0; - mVersion = 0.0f; -} - -void EsmFile::setFileName(const QString &fileName) -{ - mFileName = fileName; -} - -void EsmFile::setAuthor(const QString &author) -{ - mAuthor = author; -} - -void EsmFile::setSize(const int size) -{ - mSize = size; -} - -void EsmFile::setDates(const QDateTime &modified, const QDateTime &accessed) -{ - mModified = modified; - mAccessed = accessed; -} - -void EsmFile::setVersion(float version) -{ - mVersion = version; -} - -void EsmFile::setPath(const QString &path) -{ - mPath = path; -} - -void EsmFile::setMasters(const QStringList &masters) -{ - mMasters = masters; -} - -void EsmFile::setDescription(const QString &description) -{ - mDescription = description; -} diff --git a/components/esxselector/model/esm/esmfile.hpp b/components/esxselector/model/esm/esmfile.hpp deleted file mode 100644 index 52b3fbd007..0000000000 --- a/components/esxselector/model/esm/esmfile.hpp +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef ESMFILE_HPP -#define ESMFILE_HPP - -#include -#include - -#include "../modelitem.hpp" - -class EsmFile : public ModelItem -{ - Q_OBJECT - Q_PROPERTY(QString filename READ fileName) - -public: - EsmFile(QString fileName = QString(), ModelItem *parent = 0); - - ~EsmFile() - {} - - void setFileName(const QString &fileName); - void setAuthor(const QString &author); - void setSize(const int size); - void setDates(const QDateTime &modified, const QDateTime &accessed); - void setVersion(const float version); - void setPath(const QString &path); - void setMasters(const QStringList &masters); - void setDescription(const QString &description); - - inline QString fileName() const { return mFileName; } - inline QString author() const { return mAuthor; } - inline int size() const { return mSize; } - inline QDateTime modified() const { return mModified; } - inline QDateTime accessed() const { return mAccessed; } - inline float version() const { return mVersion; } - inline QString path() const { return mPath; } - inline QStringList masters() const { return mMasters; } - inline QString description() const { return mDescription; } - - -private: - QString mFileName; - QString mAuthor; - int mSize; - QDateTime mModified; - QDateTime mAccessed; - float mVersion; - QString mPath; - QStringList mMasters; - QString mDescription; - -}; - - -#endif diff --git a/components/esxselector/masterproxymodel.cpp b/components/esxselector/model/masterproxymodel.cpp similarity index 57% rename from components/esxselector/masterproxymodel.cpp rename to components/esxselector/model/masterproxymodel.cpp index 7701d4f17a..04a7f0033f 100644 --- a/components/esxselector/masterproxymodel.cpp +++ b/components/esxselector/model/masterproxymodel.cpp @@ -1,6 +1,6 @@ #include "masterproxymodel.hpp" -FileOrderList::MasterProxyModel::MasterProxyModel(QObject *parent, QAbstractTableModel* model) : +EsxSelector::MasterProxyModel::MasterProxyModel(QObject *parent, QAbstractTableModel* model) : QSortFilterProxyModel(parent) { setFilterRegExp(QString("game")); @@ -10,7 +10,7 @@ FileOrderList::MasterProxyModel::MasterProxyModel(QObject *parent, QAbstractTabl setSourceModel (model); } -QVariant FileOrderList::MasterProxyModel::data(const QModelIndex &index, int role) const +QVariant EsxSelector::MasterProxyModel::data(const QModelIndex &index, int role) const { return QSortFilterProxyModel::data (index, role); } diff --git a/components/esxselector/masterproxymodel.hpp b/components/esxselector/model/masterproxymodel.hpp similarity index 95% rename from components/esxselector/masterproxymodel.hpp rename to components/esxselector/model/masterproxymodel.hpp index 49ca369deb..6fbdd31545 100644 --- a/components/esxselector/masterproxymodel.hpp +++ b/components/esxselector/model/masterproxymodel.hpp @@ -5,7 +5,7 @@ class QAbstractTableModel; -namespace FileOrderList +namespace EsxSelector { class MasterProxyModel : public QSortFilterProxyModel { diff --git a/components/esxselector/utils/naturalsort.cpp b/components/esxselector/model/naturalsort.cpp similarity index 100% rename from components/esxselector/utils/naturalsort.cpp rename to components/esxselector/model/naturalsort.cpp diff --git a/components/esxselector/utils/naturalsort.hpp b/components/esxselector/model/naturalsort.hpp similarity index 100% rename from components/esxselector/utils/naturalsort.hpp rename to components/esxselector/model/naturalsort.hpp diff --git a/components/esxselector/utils/comboboxlineedit.cpp b/components/esxselector/view/comboboxlineedit.cpp similarity index 100% rename from components/esxselector/utils/comboboxlineedit.cpp rename to components/esxselector/view/comboboxlineedit.cpp diff --git a/components/esxselector/utils/comboboxlineedit.hpp b/components/esxselector/view/comboboxlineedit.hpp similarity index 100% rename from components/esxselector/utils/comboboxlineedit.hpp rename to components/esxselector/view/comboboxlineedit.hpp diff --git a/components/esxselector/contentselector.cpp b/components/esxselector/view/contentselector.cpp similarity index 74% rename from components/esxselector/contentselector.cpp rename to components/esxselector/view/contentselector.cpp index 27be996fc8..16139d9e69 100644 --- a/components/esxselector/contentselector.cpp +++ b/components/esxselector/view/contentselector.cpp @@ -10,19 +10,19 @@ #include #include -FileOrderList::ContentSelector::ContentSelector(QWidget *parent) : +EsxSelector::ContentSelector::ContentSelector(QWidget *parent) : QWidget(parent) { setupUi(this); buildModelsAndViews(); } -void FileOrderList::ContentSelector::buildModelsAndViews() +void EsxSelector::ContentSelector::buildModelsAndViews() { // Models mDataFilesModel = new DataFilesModel (this); - mMasterProxyModel = new FileOrderList::MasterProxyModel (this, mDataFilesModel); + mMasterProxyModel = new EsxSelector::MasterProxyModel (this, mDataFilesModel); mPluginsProxyModel = new PluginsProxyModel (this, mDataFilesModel); masterView->setModel(mMasterProxyModel); @@ -35,7 +35,7 @@ void FileOrderList::ContentSelector::buildModelsAndViews() connect(profilesComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCurrentProfileIndexChanged(int))); } -void FileOrderList::ContentSelector::addFiles(const QString &path) +void EsxSelector::ContentSelector::addFiles(const QString &path) { mDataFilesModel->addFiles(path); mDataFilesModel->sort(3); // Sort by date accessed @@ -43,12 +43,12 @@ void FileOrderList::ContentSelector::addFiles(const QString &path) mDataFilesModel->uncheckAll(); } -void FileOrderList::ContentSelector::setEncoding(const QString &encoding) +void EsxSelector::ContentSelector::setEncoding(const QString &encoding) { mDataFilesModel->setEncoding(encoding); } -void FileOrderList::ContentSelector::setCheckState(QModelIndex index, QSortFilterProxyModel *model) +void EsxSelector::ContentSelector::setCheckState(QModelIndex index, QSortFilterProxyModel *model) { if (!index.isValid()) return; @@ -66,12 +66,12 @@ void FileOrderList::ContentSelector::setCheckState(QModelIndex index, QSortFilte } } -QStringList FileOrderList::ContentSelector::checkedItemsPaths() +QStringList EsxSelector::ContentSelector::checkedItemsPaths() { return mDataFilesModel->checkedItemsPaths(); } -void FileOrderList::ContentSelector::updateViews() +void EsxSelector::ContentSelector::updateViews() { // Ensure the columns are hidden because sort() re-enables them pluginView->setColumnHidden(1, true); @@ -85,12 +85,12 @@ void FileOrderList::ContentSelector::updateViews() } -void FileOrderList::ContentSelector::slotCurrentProfileIndexChanged(int index) +void EsxSelector::ContentSelector::slotCurrentProfileIndexChanged(int index) { emit profileChanged(index); } -void FileOrderList::ContentSelector::slotCurrentMasterIndexChanged(int index) +void EsxSelector::ContentSelector::slotCurrentMasterIndexChanged(int index) { QObject *object = QObject::sender(); @@ -101,7 +101,7 @@ void FileOrderList::ContentSelector::slotCurrentMasterIndexChanged(int index) setCheckState(mMasterProxyModel->index(index, 0), mMasterProxyModel); } -void FileOrderList::ContentSelector::slotPluginTableItemClicked(const QModelIndex &index) +void EsxSelector::ContentSelector::slotPluginTableItemClicked(const QModelIndex &index) { setCheckState(index, mPluginsProxyModel); } diff --git a/components/esxselector/contentselector.hpp b/components/esxselector/view/contentselector.hpp similarity index 100% rename from components/esxselector/contentselector.hpp rename to components/esxselector/view/contentselector.hpp diff --git a/components/esxselector/utils/lineedit.cpp b/components/esxselector/view/lineedit.cpp similarity index 100% rename from components/esxselector/utils/lineedit.cpp rename to components/esxselector/view/lineedit.cpp diff --git a/components/esxselector/utils/lineedit.hpp b/components/esxselector/view/lineedit.hpp similarity index 100% rename from components/esxselector/utils/lineedit.hpp rename to components/esxselector/view/lineedit.hpp diff --git a/components/esxselector/utils/profilescombobox.cpp b/components/esxselector/view/profilescombobox.cpp similarity index 100% rename from components/esxselector/utils/profilescombobox.cpp rename to components/esxselector/view/profilescombobox.cpp diff --git a/components/esxselector/utils/profilescombobox.hpp b/components/esxselector/view/profilescombobox.hpp similarity index 100% rename from components/esxselector/utils/profilescombobox.hpp rename to components/esxselector/view/profilescombobox.hpp From f6217f9c6ac160d6372236698f1abcf24b3c785c Mon Sep 17 00:00:00 2001 From: graffy76 Date: Sun, 18 Aug 2013 15:11:29 -0500 Subject: [PATCH 017/434] Moved esx selector classes out of global namespace --- apps/launcher/datafilespage.cpp | 14 ++-- apps/launcher/datafilespage.hpp | 4 +- apps/launcher/graphicspage.cpp | 2 +- apps/launcher/utils/textinputdialog.cpp | 4 +- apps/launcher/utils/textinputdialog.hpp | 10 ++- apps/opencs/editor.hpp | 2 +- apps/opencs/view/doc/filedialog.cpp | 26 +++--- apps/opencs/view/doc/filedialog.hpp | 54 +++++++------ components/CMakeLists.txt | 2 +- .../esxselector/model/datafilesmodel.cpp | 53 ++++++------ .../esxselector/model/datafilesmodel.hpp | 81 ++++++++++--------- components/esxselector/model/esmfile.cpp | 50 ++++++++++++ components/esxselector/model/esmfile.hpp | 56 +++++++++++++ .../esxselector/model/masterproxymodel.cpp | 4 +- .../esxselector/model/masterproxymodel.hpp | 2 +- components/esxselector/model/modelitem.cpp | 18 ++--- components/esxselector/model/modelitem.hpp | 37 +++++---- components/esxselector/model/naturalsort.hpp | 8 +- .../esxselector/model/pluginsproxymodel.cpp | 6 +- .../esxselector/model/pluginsproxymodel.hpp | 20 +++-- .../esxselector/view/comboboxlineedit.cpp | 6 +- .../esxselector/view/comboboxlineedit.hpp | 26 +++--- .../esxselector/view/contentselector.cpp | 32 ++++---- .../esxselector/view/contentselector.hpp | 19 +++-- components/esxselector/view/lineedit.cpp | 6 +- components/esxselector/view/lineedit.hpp | 26 +++--- .../esxselector/view/profilescombobox.cpp | 12 +-- .../esxselector/view/profilescombobox.hpp | 43 +++++----- files/ui/datafilespage.ui | 8 +- 29 files changed, 381 insertions(+), 250 deletions(-) create mode 100644 components/esxselector/model/esmfile.cpp create mode 100644 components/esxselector/model/esmfile.hpp diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 44f92d7fe0..2346a0b014 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -7,15 +7,15 @@ #include -#include -#include -#include +#include +#include +#include -#include -#include -#include +#include +#include +#include -#include "components/fileorderlist/masterproxymodel.hpp" +#include "components/esxselector/model/masterproxymodel.hpp" #include "settings/gamesettings.hpp" #include "settings/launchersettings.hpp" diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index 356cb88d63..f3792b1f13 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -5,7 +5,7 @@ #include #include "ui_datafilespage.h" -#include "components/fileorderlist/contentselector.hpp" +#include "components/esxselector/view/contentselector.hpp" class QSortFilterProxyModel; class QAbstractItemModel; @@ -20,7 +20,7 @@ class PluginsProxyModel; namespace Files { struct ConfigurationManager; } -class DataFilesPage : public EsxSelector::ContentSelector +class DataFilesPage : public EsxView::ContentSelector { Q_OBJECT public: diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index 1bbf7f8973..4d5975e589 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -12,7 +12,7 @@ #include #include -#include +#include #include "settings/graphicssettings.hpp" diff --git a/apps/launcher/utils/textinputdialog.cpp b/apps/launcher/utils/textinputdialog.cpp index a4b36b95ea..052fc58e40 100644 --- a/apps/launcher/utils/textinputdialog.cpp +++ b/apps/launcher/utils/textinputdialog.cpp @@ -7,7 +7,7 @@ #include #include -#include +#include TextInputDialog::TextInputDialog(const QString& title, const QString &text, QWidget *parent) : QDialog(parent) @@ -19,7 +19,7 @@ TextInputDialog::TextInputDialog(const QString& title, const QString &text, QWid // Line edit QValidator *validator = new QRegExpValidator(QRegExp("^[a-zA-Z0-9_]*$"), this); // Alpha-numeric + underscore - mLineEdit = new LineEdit(this); + mLineEdit = new EsxView::LineEdit(this); mLineEdit->setValidator(validator); mLineEdit->setCompleter(0); diff --git a/apps/launcher/utils/textinputdialog.hpp b/apps/launcher/utils/textinputdialog.hpp index cbb453ac83..2fb6e0f6b4 100644 --- a/apps/launcher/utils/textinputdialog.hpp +++ b/apps/launcher/utils/textinputdialog.hpp @@ -5,17 +5,21 @@ //#include "lineedit.hpp" class QDialogButtonBox; -class LineEdit; + +namespace EsxView { + class LineEdit; +} + class TextInputDialog : public QDialog { Q_OBJECT public: explicit TextInputDialog(const QString& title, const QString &text, QWidget *parent = 0); - inline LineEdit *lineEdit() { return mLineEdit; } + inline EsxView::LineEdit *lineEdit() { return mLineEdit; } void setOkButtonEnabled(bool enabled); - LineEdit *mLineEdit; + EsxView::LineEdit *mLineEdit; int exec(); diff --git a/apps/opencs/editor.hpp b/apps/opencs/editor.hpp index 380e434c24..c88efcfb9b 100644 --- a/apps/opencs/editor.hpp +++ b/apps/opencs/editor.hpp @@ -22,7 +22,7 @@ namespace CS CSMDoc::DocumentManager mDocumentManager; CSVDoc::ViewManager mViewManager; CSVDoc::StartupDialogue mStartup; - FileDialog mFileDialog; + CSVDoc::FileDialog mFileDialog; Files::ConfigurationManager mCfgMgr; void setupDataFiles(); diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index 252c760faa..12849d6ee0 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -10,15 +10,15 @@ #include #include -#include -#include -#include +#include +#include +#include -#include +#include -#include "components/fileorderlist/masterproxymodel.hpp" +#include "components/esxselector/model/masterproxymodel.hpp" -FileDialog::FileDialog(QWidget *parent) : +CSVDoc::FileDialog::FileDialog(QWidget *parent) : ContentSelector(parent) { // Hide the profile elements @@ -60,7 +60,7 @@ FileDialog::FileDialog(QWidget *parent) : // connect(mButtonBox, SIGNAL(rejected()), this, SLOT(reject())); } -void FileDialog::updateOpenButton(const QStringList &items) +void CSVDoc::FileDialog::updateOpenButton(const QStringList &items) { QPushButton *openButton = mButtonBox->button(QDialogButtonBox::Open); @@ -70,7 +70,7 @@ void FileDialog::updateOpenButton(const QStringList &items) openButton->setEnabled(!items.isEmpty()); } -void FileDialog::updateCreateButton(const QString &name) +void CSVDoc::FileDialog::updateCreateButton(const QString &name) { if (!mCreateButton->isVisible()) return; @@ -78,12 +78,12 @@ void FileDialog::updateCreateButton(const QString &name) mCreateButton->setEnabled(!name.isEmpty()); } -QString FileDialog::fileName() +QString CSVDoc::FileDialog::fileName() { //return mNameLineEdit->text(); } -void FileDialog::openFile() +void CSVDoc::FileDialog::openFile() { setWindowTitle(tr("Open")); @@ -101,7 +101,7 @@ void FileDialog::openFile() activateWindow(); } -void FileDialog::newFile() +void CSVDoc::FileDialog::newFile() { setWindowTitle(tr("New")); @@ -118,12 +118,12 @@ void FileDialog::newFile() activateWindow(); } -void FileDialog::accept() +void CSVDoc::FileDialog::accept() { emit openFiles(); } -void FileDialog::createButtonClicked() +void CSVDoc::FileDialog::createButtonClicked() { emit createNewFile(); } diff --git a/apps/opencs/view/doc/filedialog.hpp b/apps/opencs/view/doc/filedialog.hpp index 1b4d09745c..d016ad32d1 100644 --- a/apps/opencs/view/doc/filedialog.hpp +++ b/apps/opencs/view/doc/filedialog.hpp @@ -4,7 +4,7 @@ #include #include -#include "components/fileorderlist/contentselector.hpp" +#include "components/esxselector/view/contentselector.hpp" #include "ui_datafilespage.h" class QDialogButtonBox; @@ -18,38 +18,40 @@ class QMenu; class DataFilesModel; class PluginsProxyModel; -class FileDialog : public EsxSelector::ContentSelector +namespace CSVDoc { - Q_OBJECT -public: - explicit FileDialog(QWidget *parent = 0); + class FileDialog : public EsxView::ContentSelector + { + Q_OBJECT + public: + explicit FileDialog(QWidget *parent = 0); - void openFile(); - void newFile(); - void accepted(); + void openFile(); + void newFile(); + void accepted(); - QString fileName(); + QString fileName(); -signals: - void openFiles(); - void createNewFile(); - -public slots: - void accept(); + signals: + void openFiles(); + void createNewFile(); -private slots: - //void updateViews(); - void updateOpenButton(const QStringList &items); - void updateCreateButton(const QString &name); + public slots: + void accept(); - void createButtonClicked(); + private slots: + //void updateViews(); + void updateOpenButton(const QStringList &items); + void updateCreateButton(const QString &name); -private: - QLabel *mNameLabel; - //LineEdit *mNameLineEdit; + void createButtonClicked(); - QPushButton *mCreateButton; - QDialogButtonBox *mButtonBox; -}; + private: + QLabel *mNameLabel; + //LineEdit *mNameLineEdit; + QPushButton *mCreateButton; + QDialogButtonBox *mButtonBox; + }; +} #endif // FILEDIALOG_HPP diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 4f14fa9d96..8b07a4e00e 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -73,7 +73,7 @@ find_package(Qt4 COMPONENTS QtCore QtGui) if(QT_QTGUI_LIBRARY AND QT_QTCORE_LIBRARY) add_component_qt_dir (esxselector model/masterproxymodel model/modelitem model/datafilesmodel - model/pluginsproxymodel model/esm/esmfile model/naturalsort + model/pluginsproxymodel model/esmfile model/naturalsort view/profilescombobox view/comboboxlineedit view/lineedit view/contentselector ) diff --git a/components/esxselector/model/datafilesmodel.cpp b/components/esxselector/model/datafilesmodel.cpp index ae842381e7..2980313f0e 100644 --- a/components/esxselector/model/datafilesmodel.cpp +++ b/components/esxselector/model/datafilesmodel.cpp @@ -2,33 +2,34 @@ #include #include #include +#include #include #include -#include "esm/esmfile.hpp" +#include "esmfile.hpp" #include "datafilesmodel.hpp" #include -DataFilesModel::DataFilesModel(QObject *parent) : +EsxModel::DataFilesModel::DataFilesModel(QObject *parent) : QAbstractTableModel(parent) { mEncoding = QString("win1252"); } -DataFilesModel::~DataFilesModel() +EsxModel::DataFilesModel::~DataFilesModel() { } -void DataFilesModel::setEncoding(const QString &encoding) +void EsxModel::DataFilesModel::setEncoding(const QString &encoding) { mEncoding = encoding; } -void DataFilesModel::setCheckState(const QModelIndex &index, Qt::CheckState state) +void EsxModel::DataFilesModel::setCheckState(const QModelIndex &index, Qt::CheckState state) { if (!index.isValid()) return; @@ -45,24 +46,24 @@ void DataFilesModel::setCheckState(const QModelIndex &index, Qt::CheckState stat } -Qt::CheckState DataFilesModel::checkState(const QModelIndex &index) +Qt::CheckState EsxModel::DataFilesModel::checkState(const QModelIndex &index) { EsmFile *file = item(index.row()); return mCheckStates[file->fileName()]; } -int DataFilesModel::columnCount(const QModelIndex &parent) const +int EsxModel::DataFilesModel::columnCount(const QModelIndex &parent) const { return parent.isValid() ? 0 : 9; } -int DataFilesModel::rowCount(const QModelIndex &parent) const +int EsxModel::DataFilesModel::rowCount(const QModelIndex &parent) const { return parent.isValid() ? 0 : mFiles.count(); } -bool DataFilesModel::moveRow(int oldrow, int row, const QModelIndex &parent) +bool EsxModel::DataFilesModel::moveRow(int oldrow, int row, const QModelIndex &parent) { if (oldrow < 0 || row < 0 || oldrow == row) return false; @@ -76,7 +77,7 @@ bool DataFilesModel::moveRow(int oldrow, int row, const QModelIndex &parent) return true; } -QVariant DataFilesModel::data(const QModelIndex &index, int role) const +QVariant EsxModel::DataFilesModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); @@ -166,7 +167,7 @@ QVariant DataFilesModel::data(const QModelIndex &index, int role) const } -Qt::ItemFlags DataFilesModel::flags(const QModelIndex &index) const +Qt::ItemFlags EsxModel::DataFilesModel::flags(const QModelIndex &index) const { if (!index.isValid()) return Qt::NoItemFlags; @@ -192,7 +193,7 @@ Qt::ItemFlags DataFilesModel::flags(const QModelIndex &index) const } -QVariant DataFilesModel::headerData(int section, Qt::Orientation orientation, int role) const +QVariant EsxModel::DataFilesModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role != Qt::DisplayRole) return QVariant(); @@ -217,7 +218,7 @@ QVariant DataFilesModel::headerData(int section, Qt::Orientation orientation, in return QVariant(); } -bool DataFilesModel::setData(const QModelIndex &index, const QVariant &value, int role) +bool EsxModel::DataFilesModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!index.isValid()) return false; @@ -225,7 +226,7 @@ bool DataFilesModel::setData(const QModelIndex &index, const QVariant &value, in return false; } -bool lessThanEsmFile(const EsmFile *e1, const EsmFile *e2) +bool lessThanEsmFile(const EsxModel::EsmFile *e1, const EsxModel::EsmFile *e2) { //Masters first then alphabetically if (e1->fileName().endsWith(".esm") && !e2->fileName().endsWith(".esm")) @@ -236,7 +237,7 @@ bool lessThanEsmFile(const EsmFile *e1, const EsmFile *e2) return e1->fileName().toLower() < e2->fileName().toLower(); } -bool lessThanDate(const EsmFile *e1, const EsmFile *e2) +bool lessThanDate(const EsxModel::EsmFile *e1, const EsxModel::EsmFile *e2) { if (e1->modified().toString(Qt::ISODate) < e2->modified().toString(Qt::ISODate)) { return true; @@ -245,7 +246,7 @@ bool lessThanDate(const EsmFile *e1, const EsmFile *e2) } } -void DataFilesModel::sort(int column, Qt::SortOrder order) +void EsxModel::DataFilesModel::sort(int column, Qt::SortOrder order) { emit layoutAboutToBeChanged(); @@ -258,14 +259,14 @@ void DataFilesModel::sort(int column, Qt::SortOrder order) emit layoutChanged(); } -void DataFilesModel::addFile(EsmFile *file) +void EsxModel::DataFilesModel::addFile(EsmFile *file) { emit beginInsertRows(QModelIndex(), mFiles.count(), mFiles.count()); mFiles.append(file); emit endInsertRows(); } -void DataFilesModel::addFiles(const QString &path) +void EsxModel::DataFilesModel::addFiles(const QString &path) { QDir dir(path); QStringList filters; @@ -330,7 +331,7 @@ void DataFilesModel::addFiles(const QString &path) delete decoder; } -QModelIndex DataFilesModel::indexFromItem(EsmFile *item) const +QModelIndex EsxModel::DataFilesModel::indexFromItem(EsmFile *item) const { if (item) return createIndex(mFiles.indexOf(item), 0); @@ -338,7 +339,7 @@ QModelIndex DataFilesModel::indexFromItem(EsmFile *item) const return QModelIndex(); } -EsmFile* DataFilesModel::findItem(const QString &name) +EsxModel::EsmFile* EsxModel::DataFilesModel::findItem(const QString &name) { QList::ConstIterator it; QList::ConstIterator itEnd = mFiles.constEnd(); @@ -356,7 +357,7 @@ EsmFile* DataFilesModel::findItem(const QString &name) return 0; } -EsmFile* DataFilesModel::item(int row) const +EsxModel::EsmFile* EsxModel::DataFilesModel::item(int row) const { if (row >= 0 && row < mFiles.count()) return mFiles.at(row); @@ -364,7 +365,7 @@ EsmFile* DataFilesModel::item(int row) const return 0; } -QStringList DataFilesModel::checkedItems() +QStringList EsxModel::DataFilesModel::checkedItems() { QStringList list; @@ -386,7 +387,7 @@ QStringList DataFilesModel::checkedItems() return list; } -QStringList DataFilesModel::checkedItemsPaths() +QStringList EsxModel::DataFilesModel::checkedItemsPaths() { QStringList list; @@ -405,14 +406,14 @@ QStringList DataFilesModel::checkedItemsPaths() return list; } -void DataFilesModel::uncheckAll() +void EsxModel::DataFilesModel::uncheckAll() { emit layoutAboutToBeChanged(); mCheckStates.clear(); emit layoutChanged(); } -QStringList DataFilesModel::uncheckedItems() +QStringList EsxModel::DataFilesModel::uncheckedItems() { QStringList list; QStringList checked = checkedItems(); @@ -433,7 +434,7 @@ QStringList DataFilesModel::uncheckedItems() return list; } -bool DataFilesModel::canBeChecked(EsmFile *file) const +bool EsxModel::DataFilesModel::canBeChecked(EsmFile *file) const { //element can be checked if all its dependencies are foreach (const QString &master, file->masters()) diff --git a/components/esxselector/model/datafilesmodel.hpp b/components/esxselector/model/datafilesmodel.hpp index 0a07a536f8..bc55bb6cff 100644 --- a/components/esxselector/model/datafilesmodel.hpp +++ b/components/esxselector/model/datafilesmodel.hpp @@ -6,61 +6,62 @@ #include #include - -class EsmFile; - -class DataFilesModel : public QAbstractTableModel +namespace EsxModel { - Q_OBJECT + class EsmFile; -public: - explicit DataFilesModel(QObject *parent = 0); - virtual ~DataFilesModel(); - virtual int columnCount(const QModelIndex &parent = QModelIndex()) const; - virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; + class DataFilesModel : public QAbstractTableModel + { + Q_OBJECT - bool moveRow(int oldrow, int row, const QModelIndex &parent = QModelIndex()); + public: + explicit DataFilesModel(QObject *parent = 0); + virtual ~DataFilesModel(); + virtual int columnCount(const QModelIndex &parent = QModelIndex()) const; + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; - virtual Qt::ItemFlags flags(const QModelIndex &index) const; + bool moveRow(int oldrow, int row, const QModelIndex &parent = QModelIndex()); - virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + virtual Qt::ItemFlags flags(const QModelIndex &index) const; - virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); - void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); + virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; - inline QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const - { return QAbstractTableModel::index(row, column, parent); } + virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); - void setEncoding(const QString &encoding); + inline QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const + { return QAbstractTableModel::index(row, column, parent); } - void addFiles(const QString &path); + void setEncoding(const QString &encoding); - void uncheckAll(); + void addFiles(const QString &path); - QStringList checkedItems(); - QStringList uncheckedItems(); - QStringList checkedItemsPaths(); + void uncheckAll(); - Qt::CheckState checkState(const QModelIndex &index); - void setCheckState(const QModelIndex &index, Qt::CheckState state); + QStringList checkedItems(); + QStringList uncheckedItems(); + QStringList checkedItemsPaths(); - QModelIndex indexFromItem(EsmFile *item) const; - EsmFile* findItem(const QString &name); - EsmFile* item(int row) const; + Qt::CheckState checkState(const QModelIndex &index); + void setCheckState(const QModelIndex &index, Qt::CheckState state); -signals: - void checkedItemsChanged(const QStringList &items); - -private: - bool canBeChecked(EsmFile *file) const; - void addFile(EsmFile *file); - - QList mFiles; - QHash mCheckStates; + QModelIndex indexFromItem(EsmFile *item) const; + EsmFile* findItem(const QString &name); + EsmFile* item(int row) const; - QString mEncoding; + signals: + void checkedItemsChanged(const QStringList &items); -}; + private: + bool canBeChecked(EsmFile *file) const; + void addFile(EsmFile *file); + QList mFiles; + QHash mCheckStates; + + QString mEncoding; + + }; +} #endif // DATAFILESMODEL_HPP diff --git a/components/esxselector/model/esmfile.cpp b/components/esxselector/model/esmfile.cpp new file mode 100644 index 0000000000..96b90e44e2 --- /dev/null +++ b/components/esxselector/model/esmfile.cpp @@ -0,0 +1,50 @@ +#include "esmfile.hpp" + +EsxModel::EsmFile::EsmFile(QString fileName, ModelItem *parent) + : ModelItem(parent) +{ + mFileName = fileName; + mSize = 0; + mVersion = 0.0f; +} + +void EsxModel::EsmFile::setFileName(const QString &fileName) +{ + mFileName = fileName; +} + +void EsxModel::EsmFile::setAuthor(const QString &author) +{ + mAuthor = author; +} + +void EsxModel::EsmFile::setSize(const int size) +{ + mSize = size; +} + +void EsxModel::EsmFile::setDates(const QDateTime &modified, const QDateTime &accessed) +{ + mModified = modified; + mAccessed = accessed; +} + +void EsxModel::EsmFile::setVersion(float version) +{ + mVersion = version; +} + +void EsxModel::EsmFile::setPath(const QString &path) +{ + mPath = path; +} + +void EsxModel::EsmFile::setMasters(const QStringList &masters) +{ + mMasters = masters; +} + +void EsxModel::EsmFile::setDescription(const QString &description) +{ + mDescription = description; +} diff --git a/components/esxselector/model/esmfile.hpp b/components/esxselector/model/esmfile.hpp new file mode 100644 index 0000000000..6a3e36b538 --- /dev/null +++ b/components/esxselector/model/esmfile.hpp @@ -0,0 +1,56 @@ +#ifndef ESMFILE_HPP +#define ESMFILE_HPP + +#include +#include + +#include "modelitem.hpp" + +namespace EsxModel +{ + class EsmFile : public ModelItem + { + Q_OBJECT + Q_PROPERTY(QString filename READ fileName) + + public: + EsmFile(QString fileName = QString(), ModelItem *parent = 0); + + ~EsmFile() + {} + + void setFileName(const QString &fileName); + void setAuthor(const QString &author); + void setSize(const int size); + void setDates(const QDateTime &modified, const QDateTime &accessed); + void setVersion(const float version); + void setPath(const QString &path); + void setMasters(const QStringList &masters); + void setDescription(const QString &description); + + inline QString fileName() const { return mFileName; } + inline QString author() const { return mAuthor; } + inline int size() const { return mSize; } + inline QDateTime modified() const { return mModified; } + inline QDateTime accessed() const { return mAccessed; } + inline float version() const { return mVersion; } + inline QString path() const { return mPath; } + inline QStringList masters() const { return mMasters; } + inline QString description() const { return mDescription; } + + + private: + QString mFileName; + QString mAuthor; + int mSize; + QDateTime mModified; + QDateTime mAccessed; + float mVersion; + QString mPath; + QStringList mMasters; + QString mDescription; + + }; +} + +#endif diff --git a/components/esxselector/model/masterproxymodel.cpp b/components/esxselector/model/masterproxymodel.cpp index 04a7f0033f..011e5ebd54 100644 --- a/components/esxselector/model/masterproxymodel.cpp +++ b/components/esxselector/model/masterproxymodel.cpp @@ -1,6 +1,6 @@ #include "masterproxymodel.hpp" -EsxSelector::MasterProxyModel::MasterProxyModel(QObject *parent, QAbstractTableModel* model) : +EsxModel::MasterProxyModel::MasterProxyModel(QObject *parent, QAbstractTableModel* model) : QSortFilterProxyModel(parent) { setFilterRegExp(QString("game")); @@ -10,7 +10,7 @@ EsxSelector::MasterProxyModel::MasterProxyModel(QObject *parent, QAbstractTableM setSourceModel (model); } -QVariant EsxSelector::MasterProxyModel::data(const QModelIndex &index, int role) const +QVariant EsxModel::MasterProxyModel::data(const QModelIndex &index, int role) const { return QSortFilterProxyModel::data (index, role); } diff --git a/components/esxselector/model/masterproxymodel.hpp b/components/esxselector/model/masterproxymodel.hpp index 6fbdd31545..fed01bdb14 100644 --- a/components/esxselector/model/masterproxymodel.hpp +++ b/components/esxselector/model/masterproxymodel.hpp @@ -5,7 +5,7 @@ class QAbstractTableModel; -namespace EsxSelector +namespace EsxModel { class MasterProxyModel : public QSortFilterProxyModel { diff --git a/components/esxselector/model/modelitem.cpp b/components/esxselector/model/modelitem.cpp index 0ff7e45cb9..8c1e83695d 100644 --- a/components/esxselector/model/modelitem.cpp +++ b/components/esxselector/model/modelitem.cpp @@ -1,23 +1,23 @@ #include "modelitem.hpp" -ModelItem::ModelItem(ModelItem *parent) +EsxModel::ModelItem::ModelItem(ModelItem *parent) : mParentItem(parent) , QObject(parent) { } -ModelItem::~ModelItem() +EsxModel::ModelItem::~ModelItem() { qDeleteAll(mChildItems); } -ModelItem *ModelItem::parent() +EsxModel::ModelItem *EsxModel::ModelItem::parent() { return mParentItem; } -int ModelItem::row() const +int EsxModel::ModelItem::row() const { if (mParentItem) return 1; @@ -28,30 +28,30 @@ int ModelItem::row() const } -int ModelItem::childCount() const +int EsxModel::ModelItem::childCount() const { return mChildItems.count(); } -int ModelItem::childRow(ModelItem *child) const +int EsxModel::ModelItem::childRow(ModelItem *child) const { Q_ASSERT(child); return mChildItems.indexOf(child); } -ModelItem *ModelItem::child(int row) +EsxModel::ModelItem *EsxModel::ModelItem::child(int row) { return mChildItems.value(row); } -void ModelItem::appendChild(ModelItem *item) +void EsxModel::ModelItem::appendChild(ModelItem *item) { mChildItems.append(item); } -void ModelItem::removeChild(int row) +void EsxModel::ModelItem::removeChild(int row) { mChildItems.removeAt(row); } diff --git a/components/esxselector/model/modelitem.hpp b/components/esxselector/model/modelitem.hpp index f4cb4322ff..64596302c8 100644 --- a/components/esxselector/model/modelitem.hpp +++ b/components/esxselector/model/modelitem.hpp @@ -4,29 +4,32 @@ #include #include -class ModelItem : public QObject +namespace EsxModel { - Q_OBJECT + class ModelItem : public QObject + { + Q_OBJECT -public: - ModelItem(ModelItem *parent = 0); - ~ModelItem(); + public: + ModelItem(ModelItem *parent = 0); + ~ModelItem(); - ModelItem *parent(); - int row() const; + ModelItem *parent(); + int row() const; - int childCount() const; - int childRow(ModelItem *child) const; - ModelItem *child(int row); + int childCount() const; + int childRow(ModelItem *child) const; + ModelItem *child(int row); - void appendChild(ModelItem *child); - void removeChild(int row); + void appendChild(ModelItem *child); + void removeChild(int row); - //virtual bool acceptChild(ModelItem *child); + //virtual bool acceptChild(ModelItem *child); -protected: - ModelItem *mParentItem; - QList mChildItems; -}; + protected: + ModelItem *mParentItem; + QList mChildItems; + }; +} #endif diff --git a/components/esxselector/model/naturalsort.hpp b/components/esxselector/model/naturalsort.hpp index 59271547a5..8386e4e9f0 100644 --- a/components/esxselector/model/naturalsort.hpp +++ b/components/esxselector/model/naturalsort.hpp @@ -3,9 +3,9 @@ #include -bool naturalSortLessThanCS( const QString &left, const QString &right ); -bool naturalSortLessThanCI( const QString &left, const QString &right ); -bool naturalSortGreaterThanCS( const QString &left, const QString &right ); -bool naturalSortGreaterThanCI( const QString &left, const QString &right ); + bool naturalSortLessThanCS( const QString &left, const QString &right ); + bool naturalSortLessThanCI( const QString &left, const QString &right ); + bool naturalSortGreaterThanCS( const QString &left, const QString &right ); + bool naturalSortGreaterThanCI( const QString &left, const QString &right ); #endif diff --git a/components/esxselector/model/pluginsproxymodel.cpp b/components/esxselector/model/pluginsproxymodel.cpp index 18aebc6b68..4fde11f470 100644 --- a/components/esxselector/model/pluginsproxymodel.cpp +++ b/components/esxselector/model/pluginsproxymodel.cpp @@ -1,7 +1,7 @@ #include "pluginsproxymodel.hpp" #include "datafilesmodel.hpp" -PluginsProxyModel::PluginsProxyModel(QObject *parent, DataFilesModel *model) : +EsxModel::PluginsProxyModel::PluginsProxyModel(QObject *parent, DataFilesModel *model) : QSortFilterProxyModel(parent) { setFilterRegExp (QString("addon")); @@ -12,11 +12,11 @@ PluginsProxyModel::PluginsProxyModel(QObject *parent, DataFilesModel *model) : setSourceModel (model); } -PluginsProxyModel::~PluginsProxyModel() +EsxModel::PluginsProxyModel::~PluginsProxyModel() { } -QVariant PluginsProxyModel::data(const QModelIndex &index, int role) const +QVariant EsxModel::PluginsProxyModel::data(const QModelIndex &index, int role) const { switch (role) { diff --git a/components/esxselector/model/pluginsproxymodel.hpp b/components/esxselector/model/pluginsproxymodel.hpp index cfade092eb..04c18c2e22 100644 --- a/components/esxselector/model/pluginsproxymodel.hpp +++ b/components/esxselector/model/pluginsproxymodel.hpp @@ -5,18 +5,22 @@ class QVariant; class QAbstractTableModel; -class DataFilesModel; -class PluginsProxyModel : public QSortFilterProxyModel +namespace EsxModel { - Q_OBJECT + class DataFilesModel; -public: + class PluginsProxyModel : public QSortFilterProxyModel + { + Q_OBJECT - explicit PluginsProxyModel(QObject *parent = 0, DataFilesModel *model = 0); - ~PluginsProxyModel(); + public: - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; -}; + explicit PluginsProxyModel(QObject *parent = 0, DataFilesModel *model = 0); + ~PluginsProxyModel(); + + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + }; +} #endif // PLUGINSPROXYMODEL_HPP diff --git a/components/esxselector/view/comboboxlineedit.cpp b/components/esxselector/view/comboboxlineedit.cpp index 4d62e1399a..815a1130b5 100644 --- a/components/esxselector/view/comboboxlineedit.cpp +++ b/components/esxselector/view/comboboxlineedit.cpp @@ -3,7 +3,7 @@ #include "comboboxlineedit.hpp" -ComboBoxLineEdit::ComboBoxLineEdit(QWidget *parent) +EsxView::ComboBoxLineEdit::ComboBoxLineEdit(QWidget *parent) : QLineEdit(parent) { mClearButton = new QToolButton(this); @@ -21,7 +21,7 @@ ComboBoxLineEdit::ComboBoxLineEdit(QWidget *parent) setStyleSheet(QString("ComboBoxLineEdit { background-color: transparent; padding-right: %1px; } ").arg(mClearButton->sizeHint().width() + frameWidth + 1)); } -void ComboBoxLineEdit::resizeEvent(QResizeEvent *) +void EsxView::ComboBoxLineEdit::resizeEvent(QResizeEvent *) { QSize sz = mClearButton->sizeHint(); int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); @@ -29,7 +29,7 @@ void ComboBoxLineEdit::resizeEvent(QResizeEvent *) (rect().bottom() + 1 - sz.height())/2); } -void ComboBoxLineEdit::updateClearButton(const QString& text) +void EsxView::ComboBoxLineEdit::updateClearButton(const QString& text) { mClearButton->setVisible(!text.isEmpty()); } diff --git a/components/esxselector/view/comboboxlineedit.hpp b/components/esxselector/view/comboboxlineedit.hpp index ba10731ae3..f3b251955d 100644 --- a/components/esxselector/view/comboboxlineedit.hpp +++ b/components/esxselector/view/comboboxlineedit.hpp @@ -14,22 +14,24 @@ class QToolButton; -class ComboBoxLineEdit : public QLineEdit +namespace EsxView { - Q_OBJECT + class ComboBoxLineEdit : public QLineEdit + { + Q_OBJECT -public: - ComboBoxLineEdit(QWidget *parent = 0); + public: + ComboBoxLineEdit(QWidget *parent = 0); -protected: - void resizeEvent(QResizeEvent *); + protected: + void resizeEvent(QResizeEvent *); -private slots: - void updateClearButton(const QString &text); - -private: - QToolButton *mClearButton; -}; + private slots: + void updateClearButton(const QString &text); + private: + QToolButton *mClearButton; + }; +} #endif // LIENEDIT_H diff --git a/components/esxselector/view/contentselector.cpp b/components/esxselector/view/contentselector.cpp index 16139d9e69..2de68e5bf8 100644 --- a/components/esxselector/view/contentselector.cpp +++ b/components/esxselector/view/contentselector.cpp @@ -1,8 +1,8 @@ #include "contentselector.hpp" -#include "model/datafilesmodel.hpp" -#include "masterproxymodel.hpp" -#include "model/pluginsproxymodel.hpp" +#include "../model/datafilesmodel.hpp" +#include "../model/masterproxymodel.hpp" +#include "../model/pluginsproxymodel.hpp" #include @@ -10,20 +10,20 @@ #include #include -EsxSelector::ContentSelector::ContentSelector(QWidget *parent) : +EsxView::ContentSelector::ContentSelector(QWidget *parent) : QWidget(parent) { setupUi(this); buildModelsAndViews(); } -void EsxSelector::ContentSelector::buildModelsAndViews() +void EsxView::ContentSelector::buildModelsAndViews() { // Models - mDataFilesModel = new DataFilesModel (this); + mDataFilesModel = new EsxModel::DataFilesModel (this); - mMasterProxyModel = new EsxSelector::MasterProxyModel (this, mDataFilesModel); - mPluginsProxyModel = new PluginsProxyModel (this, mDataFilesModel); + mMasterProxyModel = new EsxModel::MasterProxyModel (this, mDataFilesModel); + mPluginsProxyModel = new EsxModel::PluginsProxyModel (this, mDataFilesModel); masterView->setModel(mMasterProxyModel); pluginView->setModel(mPluginsProxyModel); @@ -35,7 +35,7 @@ void EsxSelector::ContentSelector::buildModelsAndViews() connect(profilesComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCurrentProfileIndexChanged(int))); } -void EsxSelector::ContentSelector::addFiles(const QString &path) +void EsxView::ContentSelector::addFiles(const QString &path) { mDataFilesModel->addFiles(path); mDataFilesModel->sort(3); // Sort by date accessed @@ -43,12 +43,12 @@ void EsxSelector::ContentSelector::addFiles(const QString &path) mDataFilesModel->uncheckAll(); } -void EsxSelector::ContentSelector::setEncoding(const QString &encoding) +void EsxView::ContentSelector::setEncoding(const QString &encoding) { mDataFilesModel->setEncoding(encoding); } -void EsxSelector::ContentSelector::setCheckState(QModelIndex index, QSortFilterProxyModel *model) +void EsxView::ContentSelector::setCheckState(QModelIndex index, QSortFilterProxyModel *model) { if (!index.isValid()) return; @@ -66,12 +66,12 @@ void EsxSelector::ContentSelector::setCheckState(QModelIndex index, QSortFilterP } } -QStringList EsxSelector::ContentSelector::checkedItemsPaths() +QStringList EsxView::ContentSelector::checkedItemsPaths() { return mDataFilesModel->checkedItemsPaths(); } -void EsxSelector::ContentSelector::updateViews() +void EsxView::ContentSelector::updateViews() { // Ensure the columns are hidden because sort() re-enables them pluginView->setColumnHidden(1, true); @@ -85,12 +85,12 @@ void EsxSelector::ContentSelector::updateViews() } -void EsxSelector::ContentSelector::slotCurrentProfileIndexChanged(int index) +void EsxView::ContentSelector::slotCurrentProfileIndexChanged(int index) { emit profileChanged(index); } -void EsxSelector::ContentSelector::slotCurrentMasterIndexChanged(int index) +void EsxView::ContentSelector::slotCurrentMasterIndexChanged(int index) { QObject *object = QObject::sender(); @@ -101,7 +101,7 @@ void EsxSelector::ContentSelector::slotCurrentMasterIndexChanged(int index) setCheckState(mMasterProxyModel->index(index, 0), mMasterProxyModel); } -void EsxSelector::ContentSelector::slotPluginTableItemClicked(const QModelIndex &index) +void EsxView::ContentSelector::slotPluginTableItemClicked(const QModelIndex &index) { setCheckState(index, mPluginsProxyModel); } diff --git a/components/esxselector/view/contentselector.hpp b/components/esxselector/view/contentselector.hpp index f1d8f6927a..35ef3a07c8 100644 --- a/components/esxselector/view/contentselector.hpp +++ b/components/esxselector/view/contentselector.hpp @@ -5,23 +5,26 @@ #include "ui_datafilespage.h" -class DataFilesModel; -class PluginsProxyModel; +namespace EsxModel +{ + class DataFilesModel; + class PluginsProxyModel; + class MasterProxyModel; +} + class QSortFilterProxyModel; -namespace FileOrderList +namespace EsxView { - class MasterProxyModel; - class ContentSelector : public QWidget, protected Ui::DataFilesPage { Q_OBJECT protected: - DataFilesModel *mDataFilesModel; - MasterProxyModel *mMasterProxyModel; - PluginsProxyModel *mPluginsProxyModel; + EsxModel::DataFilesModel *mDataFilesModel; + EsxModel::MasterProxyModel *mMasterProxyModel; + EsxModel::PluginsProxyModel *mPluginsProxyModel; public: explicit ContentSelector(QWidget *parent = 0); diff --git a/components/esxselector/view/lineedit.cpp b/components/esxselector/view/lineedit.cpp index b0f3395897..8944251ae8 100644 --- a/components/esxselector/view/lineedit.cpp +++ b/components/esxselector/view/lineedit.cpp @@ -3,7 +3,7 @@ #include "lineedit.hpp" -LineEdit::LineEdit(QWidget *parent) +EsxView::LineEdit::LineEdit(QWidget *parent) : QLineEdit(parent) { mClearButton = new QToolButton(this); @@ -24,7 +24,7 @@ LineEdit::LineEdit(QWidget *parent) qMax(msz.height(), mClearButton->sizeHint().height() + frameWidth * 2 + 2)); } -void LineEdit::resizeEvent(QResizeEvent *) +void EsxView::LineEdit::resizeEvent(QResizeEvent *) { QSize sz = mClearButton->sizeHint(); int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); @@ -32,7 +32,7 @@ void LineEdit::resizeEvent(QResizeEvent *) (rect().bottom() + 1 - sz.height())/2); } -void LineEdit::updateClearButton(const QString& text) +void EsxView::LineEdit::updateClearButton(const QString& text) { mClearButton->setVisible(!text.isEmpty()); } diff --git a/components/esxselector/view/lineedit.hpp b/components/esxselector/view/lineedit.hpp index 14bd7b1b4c..e48392ba86 100644 --- a/components/esxselector/view/lineedit.hpp +++ b/components/esxselector/view/lineedit.hpp @@ -14,22 +14,24 @@ class QToolButton; -class LineEdit : public QLineEdit +namespace EsxView { - Q_OBJECT + class LineEdit : public QLineEdit + { + Q_OBJECT -public: - LineEdit(QWidget *parent = 0); + public: + LineEdit(QWidget *parent = 0); -protected: - void resizeEvent(QResizeEvent *); + protected: + void resizeEvent(QResizeEvent *); -private slots: - void updateClearButton(const QString &text); - -private: - QToolButton *mClearButton; -}; + private slots: + void updateClearButton(const QString &text); + private: + QToolButton *mClearButton; + }; +} #endif // LIENEDIT_H diff --git a/components/esxselector/view/profilescombobox.cpp b/components/esxselector/view/profilescombobox.cpp index 9346276dae..b765f87ca2 100644 --- a/components/esxselector/view/profilescombobox.cpp +++ b/components/esxselector/view/profilescombobox.cpp @@ -7,7 +7,7 @@ #include "profilescombobox.hpp" #include "comboboxlineedit.hpp" -ProfilesComboBox::ProfilesComboBox(QWidget *parent) : +EsxView::ProfilesComboBox::ProfilesComboBox(QWidget *parent) : QComboBox(parent) { mValidator = new QRegExpValidator(QRegExp("^[a-zA-Z0-9_]*$"), this); // Alpha-numeric + underscore @@ -21,7 +21,7 @@ ProfilesComboBox::ProfilesComboBox(QWidget *parent) : setInsertPolicy(QComboBox::NoInsert); } -void ProfilesComboBox::setEditEnabled(bool editable) +void EsxView::ProfilesComboBox::setEditEnabled(bool editable) { if (isEditable() == editable) return; @@ -47,7 +47,7 @@ void ProfilesComboBox::setEditEnabled(bool editable) SLOT(slotTextChanged(QString))); } -void ProfilesComboBox::slotTextChanged(const QString &text) +void EsxView::ProfilesComboBox::slotTextChanged(const QString &text) { QPalette *palette = new QPalette(); palette->setColor(QPalette::Text,Qt::red); @@ -61,7 +61,7 @@ void ProfilesComboBox::slotTextChanged(const QString &text) } } -void ProfilesComboBox::slotEditingFinished() +void EsxView::ProfilesComboBox::slotEditingFinished() { QString current = currentText(); QString previous = itemText(currentIndex()); @@ -82,7 +82,7 @@ void ProfilesComboBox::slotEditingFinished() emit(profileRenamed(previous, current)); } -void ProfilesComboBox::slotIndexChanged(int index) +void EsxView::ProfilesComboBox::slotIndexChanged(int index) { if (index == -1) return; @@ -91,7 +91,7 @@ void ProfilesComboBox::slotIndexChanged(int index) mOldProfile = itemText(index); } -void ProfilesComboBox::paintEvent(QPaintEvent *) +void EsxView::ProfilesComboBox::paintEvent(QPaintEvent *) { QStylePainter painter(this); painter.setPen(palette().color(QPalette::Text)); diff --git a/components/esxselector/view/profilescombobox.hpp b/components/esxselector/view/profilescombobox.hpp index 55913d7fec..218948e7bc 100644 --- a/components/esxselector/view/profilescombobox.hpp +++ b/components/esxselector/view/profilescombobox.hpp @@ -6,28 +6,31 @@ class QString; class QRegExpValidator; -class ProfilesComboBox : public QComboBox +namespace EsxView { - Q_OBJECT -public: - explicit ProfilesComboBox(QWidget *parent = 0); - void setEditEnabled(bool editable); - -signals: - void profileChanged(const QString &previous, const QString ¤t); - void profileRenamed(const QString &oldName, const QString &newName); - -private slots: - void slotEditingFinished(); - void slotIndexChanged(int index); - void slotTextChanged(const QString &text); + class ProfilesComboBox : public QComboBox + { + Q_OBJECT + public: + explicit ProfilesComboBox(QWidget *parent = 0); + void setEditEnabled(bool editable); -private: - QString mOldProfile; - QRegExpValidator *mValidator; + signals: + void profileChanged(const QString &previous, const QString ¤t); + void profileRenamed(const QString &oldName, const QString &newName); -protected: - void paintEvent(QPaintEvent *); -}; + private slots: + void slotEditingFinished(); + void slotIndexChanged(int index); + void slotTextChanged(const QString &text); + + private: + QString mOldProfile; + QRegExpValidator *mValidator; + + protected: + void paintEvent(QPaintEvent *); + }; +} #endif // PROFILESCOMBOBOX_HPP diff --git a/files/ui/datafilespage.ui b/files/ui/datafilespage.ui index 07ad9d3ba6..523ee69cca 100644 --- a/files/ui/datafilespage.ui +++ b/files/ui/datafilespage.ui @@ -17,7 +17,7 @@ - + false @@ -79,7 +79,7 @@ - + true @@ -168,9 +168,9 @@ - ProfilesComboBox + EsxView::ProfilesComboBox QComboBox -
components/fileorderlist/utils/profilescombobox.hpp
+
components/esxselector/view/profilescombobox.hpp
From e614ec335334d186dac0fba1147fa4c0e5bd6400 Mon Sep 17 00:00:00 2001 From: graffy76 Date: Sun, 18 Aug 2013 17:11:23 -0500 Subject: [PATCH 018/434] Fixing profile code in progress... --- apps/opencs/view/doc/filedialog.cpp | 11 +- apps/opencs/view/doc/filedialog.hpp | 8 +- .../esxselector/view/contentselector.cpp | 4 +- .../esxselector/view/profilescombobox.cpp | 7 +- .../esxselector/view/profilescombobox.hpp | 2 + files/ui/datafilespage.ui | 257 +++++++++++------- 6 files changed, 175 insertions(+), 114 deletions(-) diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index 12849d6ee0..4f4aef4f23 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -22,10 +22,7 @@ CSVDoc::FileDialog::FileDialog(QWidget *parent) : ContentSelector(parent) { // Hide the profile elements - profileLabel->hide(); - profilesComboBox->hide(); - newProfileButton->hide(); - deleteProfileButton->hide(); + profileGroupBox->hide(); // Add some extra widgets QHBoxLayout *nameLayout = new QHBoxLayout(); @@ -34,12 +31,12 @@ CSVDoc::FileDialog::FileDialog(QWidget *parent) : mNameLabel = new QLabel(tr("File Name:"), this); QRegExpValidator *validator = new QRegExpValidator(QRegExp("^[a-zA-Z0-9\\s]*$")); - //mNameLineEdit = new LineEdit(this); - //mNameLineEdit->setValidator(validator); + mNameLineEdit = new EsxView::LineEdit(this); + mNameLineEdit->setValidator(validator); nameLayout->addSpacerItem(spacer); nameLayout->addWidget(mNameLabel); - //nameLayout->addWidget(mNameLineEdit); + nameLayout->addWidget(mNameLineEdit); mButtonBox = new QDialogButtonBox(this); diff --git a/apps/opencs/view/doc/filedialog.hpp b/apps/opencs/view/doc/filedialog.hpp index d016ad32d1..0e2d8f32b8 100644 --- a/apps/opencs/view/doc/filedialog.hpp +++ b/apps/opencs/view/doc/filedialog.hpp @@ -14,10 +14,16 @@ class QPushButton; class QStringList; class QString; class QMenu; +class QLabel; class DataFilesModel; class PluginsProxyModel; +namespace EsxView +{ + class LineEdit; +} + namespace CSVDoc { class FileDialog : public EsxView::ContentSelector @@ -48,7 +54,7 @@ namespace CSVDoc private: QLabel *mNameLabel; - //LineEdit *mNameLineEdit; + EsxView::LineEdit *mNameLineEdit; QPushButton *mCreateButton; QDialogButtonBox *mButtonBox; diff --git a/components/esxselector/view/contentselector.cpp b/components/esxselector/view/contentselector.cpp index 2de68e5bf8..266fd76dcb 100644 --- a/components/esxselector/view/contentselector.cpp +++ b/components/esxselector/view/contentselector.cpp @@ -25,9 +25,11 @@ void EsxView::ContentSelector::buildModelsAndViews() mMasterProxyModel = new EsxModel::MasterProxyModel (this, mDataFilesModel); mPluginsProxyModel = new EsxModel::PluginsProxyModel (this, mDataFilesModel); + masterView->setPlaceholderText(QString("Select a game file...")); masterView->setModel(mMasterProxyModel); pluginView->setModel(mPluginsProxyModel); - pluginView-> + profilesComboBox->setPlaceholderText(QString("Select a profile...")); + connect(mDataFilesModel, SIGNAL(layoutChanged()), this, SLOT(updateViews())); connect(pluginView, SIGNAL(clicked(const QModelIndex &)), this, SLOT(slotPluginTableItemClicked(const QModelIndex &))); diff --git a/components/esxselector/view/profilescombobox.cpp b/components/esxselector/view/profilescombobox.cpp index b765f87ca2..0d709aa50c 100644 --- a/components/esxselector/view/profilescombobox.cpp +++ b/components/esxselector/view/profilescombobox.cpp @@ -103,6 +103,11 @@ void EsxView::ProfilesComboBox::paintEvent(QPaintEvent *) // draw the icon and text if (!opt.editable && currentIndex() == -1) // <<< we adjust the text displayed when nothing is selected - opt.currentText = tr("Select a game file..."); + opt.currentText = mPlaceholderText; painter.drawControl(QStyle::CE_ComboBoxLabel, opt); } + +void EsxView::ProfilesComboBox::setPlaceholderText(const QString &text) +{ + mPlaceholderText = text; +} diff --git a/components/esxselector/view/profilescombobox.hpp b/components/esxselector/view/profilescombobox.hpp index 218948e7bc..28740783ba 100644 --- a/components/esxselector/view/profilescombobox.hpp +++ b/components/esxselector/view/profilescombobox.hpp @@ -14,6 +14,7 @@ namespace EsxView public: explicit ProfilesComboBox(QWidget *parent = 0); void setEditEnabled(bool editable); + void setPlaceholderText (const QString &text); signals: void profileChanged(const QString &previous, const QString ¤t); @@ -26,6 +27,7 @@ namespace EsxView private: QString mOldProfile; + QString mPlaceholderText; QRegExpValidator *mValidator; protected: diff --git a/files/ui/datafilespage.ui b/files/ui/datafilespage.ui index 523ee69cca..6235f31afd 100644 --- a/files/ui/datafilespage.ui +++ b/files/ui/datafilespage.ui @@ -14,113 +14,162 @@ Qt::DefaultContextMenu + + 6 + + + 6 + - - - - - false - - - - + + + Content + + + + 9 + + + 6 + + + 6 + + + + + + + false + + + + + + + + + + + + 0 + 0 + + + + Qt::DefaultContextMenu + + + QAbstractItemView::NoEditTriggers + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + Qt::ElideLeft + + + false + + + false + + + true + + + false + + + + + + + pluginView + masterView + pluginView + masterView + + + - - - - - - 0 - 0 - - - - Qt::DefaultContextMenu - - - QAbstractItemView::NoEditTriggers - - - true - - - QAbstractItemView::SingleSelection - - - QAbstractItemView::SelectRows - - - Qt::ElideLeft - - - false - - - false - - - true - - - false - - - - - - - - - - - Current Profile: - - - - - - - true - - - - 0 - 0 - - - - - - - - New Profile - - - &New Profile - - - true - - - - - - - Delete Profile - - - Delete Profile - - - Ctrl+D - - - true - - - - + + + Qt::NoFocus + + + Profiles + + + false + + + + 6 + + + 9 + + + 9 + + + 0 + + + 6 + + + + + true + + + + 0 + 0 + + + + + + + + New Profile + + + &New Profile + + + true + + + + + + + Delete Profile + + + Delete Profile + + + Ctrl+D + + + true + + + + + From b52645bf2ac594b6762fabf2b79943fd3e42d8c7 Mon Sep 17 00:00:00 2001 From: graffy76 Date: Tue, 20 Aug 2013 03:23:32 -0500 Subject: [PATCH 019/434] Fixes to accommodate master/plugin loading --- apps/launcher/datafilespage.cpp | 96 ++----------------- apps/opencs/view/doc/filedialog.cpp | 9 +- .../esxselector/model/datafilesmodel.cpp | 34 ++++--- .../esxselector/model/datafilesmodel.hpp | 10 +- .../esxselector/view/contentselector.cpp | 1 + files/ui/datafilespage.ui | 4 +- 6 files changed, 38 insertions(+), 116 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 2346a0b014..15f8d9ba27 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -29,16 +29,11 @@ DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gam , mLauncherSettings(launcherSettings) , ContentSelector(parent) { - - pluginView->hideColumn(2); // Create a dialog for the new profile name input mNewProfileDialog = new TextInputDialog(tr("New Profile"), tr("Profile name:"), this); connect(mNewProfileDialog->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(updateOkButton(QString))); - //connect(pluginView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint))); - //connect(masterView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint))); - createActions(); setupDataFiles(); } @@ -49,11 +44,6 @@ void DataFilesPage::createActions() // Add the actions to the toolbuttons newProfileButton->setDefaultAction(newProfileAction); deleteProfileButton->setDefaultAction(deleteProfileAction); - - // Context menu actions - mContextMenu = new QMenu(this); - mContextMenu->addAction(checkAction); - mContextMenu->addAction(uncheckAction); } void DataFilesPage::setupDataFiles() @@ -150,17 +140,17 @@ void DataFilesPage::saveSettings() mGameSettings.remove(QString("master")); mGameSettings.remove(QString("plugin")); - QStringList items = mDataFilesModel->checkedItems(); + EsxModel::EsmFileList items = mDataFilesModel->checkedItems(); - foreach(const QString &item, items) { + foreach(const EsxModel::EsmFile *item, items) { - if (item.endsWith(QString(".esm"), Qt::CaseInsensitive)) { - mLauncherSettings.setMultiValue(QString("Profiles/") + profile + QString("/master"), item); - mGameSettings.setMultiValue(QString("master"), item); + if (item->masters().size() == 0) { + mLauncherSettings.setMultiValue(QString("Profiles/") + profile + QString("/master"), item->fileName()); + mGameSettings.setMultiValue(QString("master"), item->fileName()); - } else if (item.endsWith(QString(".esp"), Qt::CaseInsensitive)) { - mLauncherSettings.setMultiValue(QString("Profiles/") + profile + QString("/plugin"), item); - mGameSettings.setMultiValue(QString("plugin"), item); + } else { + mLauncherSettings.setMultiValue(QString("Profiles/") + profile + QString("/plugin"), item->fileName()); + mGameSettings.setMultiValue(QString("plugin"), item->fileName()); } } @@ -296,73 +286,3 @@ void DataFilesPage::profileRenamed(const QString &previous, const QString &curre loadSettings(); } -/* -void DataFilesPage::showContextMenu(const QPoint &point) -{ - QObject *object = QObject::sender(); - - // Not a signal-slot call - if (!object) - return; - - if (object->objectName() == QLatin1String("PluginView")) { - if (!pluginView->selectionModel()->hasSelection()) - return; - - QPoint globalPos = pluginView->mapToGlobal(point); - QModelIndexList indexes = pluginView->selectionModel()->selectedIndexes(); - - // Show the check/uncheck actions depending on the state of the selected items - uncheckAction->setEnabled(false); - checkAction->setEnabled(false); - - foreach (const QModelIndex &index, indexes) - { - if (!index.isValid()) - return; - - QModelIndex sourceIndex = mPluginsProxyModel->mapToSource(index); - - if (!sourceIndex.isValid()) - return; - - (mDataFilesModel->checkState(sourceIndex) == Qt::Checked) - ? uncheckAction->setEnabled(true) - : checkAction->setEnabled(true); - } - - // Show menu - mContextMenu->exec(globalPos); - } - - if (object->objectName() == QLatin1String("MasterView")) { - if (!masterView->selectionModel()->hasSelection()) - return; - - QPoint globalPos = masterView->mapToGlobal(point); - QModelIndexList indexes = masterView->selectionModel()->selectedIndexes(); - - // Show the check/uncheck actions depending on the state of the selected items - uncheckAction->setEnabled(false); - checkAction->setEnabled(false); - - foreach (const QModelIndex &index, indexes) - { - if (!index.isValid()) - return; - - QModelIndex sourceIndex = mMastersProxyModel->mapToSource(index); - - if (!sourceIndex.isValid()) - return; - - (mDataFilesModel->checkState(sourceIndex) == Qt::Checked) - ? uncheckAction->setEnabled(true) - : checkAction->setEnabled(true); - } - - mContextMenu->exec(globalPos); - } - -} -*/ diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index 4f4aef4f23..69ed1c13a7 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -23,6 +23,7 @@ CSVDoc::FileDialog::FileDialog(QWidget *parent) : { // Hide the profile elements profileGroupBox->hide(); + pluginView->showColumn(2); // Add some extra widgets QHBoxLayout *nameLayout = new QHBoxLayout(); @@ -77,7 +78,7 @@ void CSVDoc::FileDialog::updateCreateButton(const QString &name) QString CSVDoc::FileDialog::fileName() { - //return mNameLineEdit->text(); + return mNameLineEdit->text(); } void CSVDoc::FileDialog::openFile() @@ -85,7 +86,7 @@ void CSVDoc::FileDialog::openFile() setWindowTitle(tr("Open")); mNameLabel->hide(); - //mNameLineEdit->hide(); + mNameLineEdit->hide(); mCreateButton->hide(); mButtonBox->removeButton(mCreateButton); @@ -103,8 +104,8 @@ void CSVDoc::FileDialog::newFile() setWindowTitle(tr("New")); mNameLabel->show(); - //mNameLineEdit->clear(); - //mNameLineEdit->show(); + mNameLineEdit->clear(); + mNameLineEdit->show(); mCreateButton->show(); mButtonBox->setStandardButtons(QDialogButtonBox::Cancel); diff --git a/components/esxselector/model/datafilesmodel.cpp b/components/esxselector/model/datafilesmodel.cpp index 2980313f0e..49d0d6132a 100644 --- a/components/esxselector/model/datafilesmodel.cpp +++ b/components/esxselector/model/datafilesmodel.cpp @@ -365,23 +365,21 @@ EsxModel::EsmFile* EsxModel::DataFilesModel::item(int row) const return 0; } -QStringList EsxModel::DataFilesModel::checkedItems() +EsxModel::EsmFileList EsxModel::DataFilesModel::checkedItems() { - QStringList list; + EsmFileList list; - QList::ConstIterator it; - QList::ConstIterator itEnd = mFiles.constEnd(); + EsmFileList::ConstIterator it; + EsmFileList::ConstIterator itEnd = mFiles.constEnd(); int i = 0; - for (it = mFiles.constBegin(); it != itEnd; ++it) { - EsmFile *file = item(i); - ++i; - - QString name = file->fileName(); + for (it = mFiles.constBegin(); it != itEnd; ++it) + { + EsmFile *file = *it; // Only add the items that are in the checked list and available - if (mCheckStates[name] == Qt::Checked && canBeChecked(file)) - list << name; + if (mCheckStates[file->fileName()] == Qt::Checked && canBeChecked(file)) + list << file; } return list; @@ -413,13 +411,13 @@ void EsxModel::DataFilesModel::uncheckAll() emit layoutChanged(); } -QStringList EsxModel::DataFilesModel::uncheckedItems() +EsxModel::EsmFileList EsxModel::DataFilesModel::uncheckedItems() { - QStringList list; - QStringList checked = checkedItems(); + EsmFileList list; + EsmFileList checked = checkedItems(); - QList::ConstIterator it; - QList::ConstIterator itEnd = mFiles.constEnd(); + EsmFileList::ConstIterator it; + EsmFileList::ConstIterator itEnd = mFiles.constEnd(); int i = 0; for (it = mFiles.constBegin(); it != itEnd; ++it) { @@ -427,8 +425,8 @@ QStringList EsxModel::DataFilesModel::uncheckedItems() ++i; // Add the items that are not in the checked list - if (!checked.contains(file->fileName())) - list << file->fileName(); + if (!checked.contains(file)) + list << file; } return list; diff --git a/components/esxselector/model/datafilesmodel.hpp b/components/esxselector/model/datafilesmodel.hpp index bc55bb6cff..24b36aa88b 100644 --- a/components/esxselector/model/datafilesmodel.hpp +++ b/components/esxselector/model/datafilesmodel.hpp @@ -10,6 +10,8 @@ namespace EsxModel { class EsmFile; + typedef QList EsmFileList; + class DataFilesModel : public QAbstractTableModel { Q_OBJECT @@ -39,8 +41,8 @@ namespace EsxModel void uncheckAll(); - QStringList checkedItems(); - QStringList uncheckedItems(); + EsmFileList checkedItems(); + EsmFileList uncheckedItems(); QStringList checkedItemsPaths(); Qt::CheckState checkState(const QModelIndex &index); @@ -51,13 +53,13 @@ namespace EsxModel EsmFile* item(int row) const; signals: - void checkedItemsChanged(const QStringList &items); + void checkedItemsChanged(const EsmFileList &items); private: bool canBeChecked(EsmFile *file) const; void addFile(EsmFile *file); - QList mFiles; + EsmFileList mFiles; QHash mCheckStates; QString mEncoding; diff --git a/components/esxselector/view/contentselector.cpp b/components/esxselector/view/contentselector.cpp index 266fd76dcb..0b47802416 100644 --- a/components/esxselector/view/contentselector.cpp +++ b/components/esxselector/view/contentselector.cpp @@ -77,6 +77,7 @@ void EsxView::ContentSelector::updateViews() { // Ensure the columns are hidden because sort() re-enables them pluginView->setColumnHidden(1, true); + pluginView->setColumnHidden(2, true); pluginView->setColumnHidden(3, true); pluginView->setColumnHidden(4, true); pluginView->setColumnHidden(5, true); diff --git a/files/ui/datafilespage.ui b/files/ui/datafilespage.ui index 6235f31afd..ecc70dfcb3 100644 --- a/files/ui/datafilespage.ui +++ b/files/ui/datafilespage.ui @@ -27,7 +27,7 @@ - 9 + 3 6 @@ -115,7 +115,7 @@ 6 - 9 + 3 9 From 24e38846da6f96b79f722cdad989672dcbb99c21 Mon Sep 17 00:00:00 2001 From: graffy76 Date: Tue, 20 Aug 2013 03:53:23 -0500 Subject: [PATCH 020/434] Fixed broken profile actions --- apps/launcher/datafilespage.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 15f8d9ba27..6f8d1dfac3 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -29,6 +29,8 @@ DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gam , mLauncherSettings(launcherSettings) , ContentSelector(parent) { + QMetaObject::connectSlotsByName(this); + // Create a dialog for the new profile name input mNewProfileDialog = new TextInputDialog(tr("New Profile"), tr("Profile name:"), this); @@ -40,10 +42,13 @@ DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gam void DataFilesPage::createActions() { - + qDebug () << "adding actions..."; // Add the actions to the toolbuttons newProfileButton->setDefaultAction(newProfileAction); deleteProfileButton->setDefaultAction(deleteProfileAction); + + for (int i = 0; i < newProfileButton->actions().size(); i++) + qDebug() << newProfileButton->actions().at(i)->objectName(); } void DataFilesPage::setupDataFiles() @@ -186,6 +191,7 @@ int DataFilesPage::profilesComboBoxIndex() void DataFilesPage::on_newProfileAction_triggered() { + qDebug() << "new_profile_action_triggered"; if (mNewProfileDialog->exec() == QDialog::Accepted) { QString profile = mNewProfileDialog->lineEdit()->text(); profilesComboBox->addItem(profile); From 6898321676e0841cd2dbc70bf93b57748199d924 Mon Sep 17 00:00:00 2001 From: graffy76 Date: Tue, 20 Aug 2013 08:16:56 -0500 Subject: [PATCH 021/434] Reenabling features Profile functions enabled New/load file functions partially enabled Layout reorganized --- apps/launcher/datafilespage.cpp | 4 +- apps/opencs/view/doc/filedialog.cpp | 67 +++++------------------- apps/opencs/view/doc/filedialog.hpp | 11 ---- components/esxselector/view/lineedit.cpp | 1 + components/esxselector/view/lineedit.hpp | 2 + files/ui/datafilespage.ui | 53 +++++++++++++++---- 6 files changed, 60 insertions(+), 78 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 6f8d1dfac3..d51952b113 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -31,6 +31,8 @@ DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gam { QMetaObject::connectSlotsByName(this); + projectGroupBox->hide(); + // Create a dialog for the new profile name input mNewProfileDialog = new TextInputDialog(tr("New Profile"), tr("Profile name:"), this); @@ -42,7 +44,6 @@ DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gam void DataFilesPage::createActions() { - qDebug () << "adding actions..."; // Add the actions to the toolbuttons newProfileButton->setDefaultAction(newProfileAction); deleteProfileButton->setDefaultAction(deleteProfileAction); @@ -191,7 +192,6 @@ int DataFilesPage::profilesComboBoxIndex() void DataFilesPage::on_newProfileAction_triggered() { - qDebug() << "new_profile_action_triggered"; if (mNewProfileDialog->exec() == QDialog::Accepted) { QString profile = mNewProfileDialog->lineEdit()->text(); profilesComboBox->addItem(profile); diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index 69ed1c13a7..a0360cc5ea 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -25,42 +25,20 @@ CSVDoc::FileDialog::FileDialog(QWidget *parent) : profileGroupBox->hide(); pluginView->showColumn(2); - // Add some extra widgets - QHBoxLayout *nameLayout = new QHBoxLayout(); - QSpacerItem *spacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - - mNameLabel = new QLabel(tr("File Name:"), this); - - QRegExpValidator *validator = new QRegExpValidator(QRegExp("^[a-zA-Z0-9\\s]*$")); - mNameLineEdit = new EsxView::LineEdit(this); - mNameLineEdit->setValidator(validator); - - nameLayout->addSpacerItem(spacer); - nameLayout->addWidget(mNameLabel); - nameLayout->addWidget(mNameLineEdit); - - mButtonBox = new QDialogButtonBox(this); - - mCreateButton = new QPushButton(tr("Create"), this); - mCreateButton->setEnabled(false); - - verticalLayout->addLayout(nameLayout); - verticalLayout->addWidget(mButtonBox); - resize(400, 400); // connect(mDataFilesModel, SIGNAL(checkedItemsChanged(QStringList)), this, SLOT(updateOpenButton(QStringList))); //connect(mNameLineEdit, SIGNAL(textChanged(QString)), this, SLOT(updateCreateButton(QString))); - // connect(mCreateButton, SIGNAL(clicked()), this, SLOT(createButtonClicked())); + connect(projectCreateButton, SIGNAL(clicked()), this, SIGNAL(createNewFile())); - // connect(mButtonBox, SIGNAL(accepted()), this, SLOT(accept())); - // connect(mButtonBox, SIGNAL(rejected()), this, SLOT(reject())); + connect(mButtonBox, SIGNAL(accepted()), this, SIGNAL(openFiles()); + // connect(mButtonBox, SIGNAL(rejected()), this, SLOT(reject())); } void CSVDoc::FileDialog::updateOpenButton(const QStringList &items) { - QPushButton *openButton = mButtonBox->button(QDialogButtonBox::Open); + QPushButton *openButton = projectButtonBox->button(QDialogButtonBox::Open); if (!openButton) return; @@ -70,29 +48,25 @@ void CSVDoc::FileDialog::updateOpenButton(const QStringList &items) void CSVDoc::FileDialog::updateCreateButton(const QString &name) { - if (!mCreateButton->isVisible()) + if (!projectCreateButton->isVisible()) return; - mCreateButton->setEnabled(!name.isEmpty()); + projectCreateButton->setEnabled(!name.isEmpty()); } QString CSVDoc::FileDialog::fileName() { - return mNameLineEdit->text(); + return projectNameLineEdit->text(); } void CSVDoc::FileDialog::openFile() { setWindowTitle(tr("Open")); - mNameLabel->hide(); - mNameLineEdit->hide(); - mCreateButton->hide(); - - mButtonBox->removeButton(mCreateButton); - mButtonBox->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Open); - QPushButton *openButton = mButtonBox->button(QDialogButtonBox::Open); - openButton->setEnabled(false); + projectNameLineEdit->hide(); + projectCreateButton->hide(); + projectGroupBox->setTitle(tr("")); + projectButtonBox->button(QDialogButtonBox::Open)->setEnabled(false); show(); raise(); @@ -103,25 +77,10 @@ void CSVDoc::FileDialog::newFile() { setWindowTitle(tr("New")); - mNameLabel->show(); - mNameLineEdit->clear(); - mNameLineEdit->show(); - mCreateButton->show(); - - mButtonBox->setStandardButtons(QDialogButtonBox::Cancel); - mButtonBox->addButton(mCreateButton, QDialogButtonBox::ActionRole); + projectButtonBox->setStandardButtons(QDialogButtonBox::Cancel); + projectButtonBox->addButton(projectCreateButton, QDialogButtonBox::ActionRole); show(); raise(); activateWindow(); } - -void CSVDoc::FileDialog::accept() -{ - emit openFiles(); -} - -void CSVDoc::FileDialog::createButtonClicked() -{ - emit createNewFile(); -} diff --git a/apps/opencs/view/doc/filedialog.hpp b/apps/opencs/view/doc/filedialog.hpp index 0e2d8f32b8..c749099d48 100644 --- a/apps/opencs/view/doc/filedialog.hpp +++ b/apps/opencs/view/doc/filedialog.hpp @@ -34,7 +34,6 @@ namespace CSVDoc void openFile(); void newFile(); - void accepted(); QString fileName(); @@ -43,21 +42,11 @@ namespace CSVDoc void createNewFile(); public slots: - void accept(); private slots: //void updateViews(); void updateOpenButton(const QStringList &items); void updateCreateButton(const QString &name); - - void createButtonClicked(); - - private: - QLabel *mNameLabel; - EsxView::LineEdit *mNameLineEdit; - - QPushButton *mCreateButton; - QDialogButtonBox *mButtonBox; }; } #endif // FILEDIALOG_HPP diff --git a/components/esxselector/view/lineedit.cpp b/components/esxselector/view/lineedit.cpp index 8944251ae8..48be2f022f 100644 --- a/components/esxselector/view/lineedit.cpp +++ b/components/esxselector/view/lineedit.cpp @@ -1,5 +1,6 @@ #include #include +#include #include "lineedit.hpp" diff --git a/components/esxselector/view/lineedit.hpp b/components/esxselector/view/lineedit.hpp index e48392ba86..4e0cbe3390 100644 --- a/components/esxselector/view/lineedit.hpp +++ b/components/esxselector/view/lineedit.hpp @@ -20,6 +20,8 @@ namespace EsxView { Q_OBJECT + QString mPlaceholderText; + public: LineEdit(QWidget *parent = 0); diff --git a/files/ui/datafilespage.ui b/files/ui/datafilespage.ui index ecc70dfcb3..60e8b8bf5d 100644 --- a/files/ui/datafilespage.ui +++ b/files/ui/datafilespage.ui @@ -14,12 +14,6 @@ Qt::DefaultContextMenu - - 6 - - - 6 - @@ -91,21 +85,53 @@
- pluginView - masterView - pluginView - masterView
+ + + + Project + + + + + + Enter project name... + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Open + + + + + + + false + + + Create + + + + + projectButtonBox + projectCreateButton + projectNameLineEdit + + Qt::NoFocus - Profiles + Profile false @@ -221,6 +247,11 @@ QComboBox
components/esxselector/view/profilescombobox.hpp
+ + EsxView::LineEdit + QLineEdit +
components/esxselector/view/lineedit.hpp
+
From e6fdc7e7fdce784b2e540022087cc90b35a0bbc4 Mon Sep 17 00:00:00 2001 From: graffy76 Date: Tue, 20 Aug 2013 12:34:39 -0500 Subject: [PATCH 022/434] ... --- apps/launcher/datafilespage.cpp | 3 --- apps/opencs/view/doc/filedialog.cpp | 8 ++++---- components/esxselector/view/contentselector.cpp | 2 +- components/esxselector/view/contentselector.hpp | 4 ++-- 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index d51952b113..070f455e4e 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -47,9 +47,6 @@ void DataFilesPage::createActions() // Add the actions to the toolbuttons newProfileButton->setDefaultAction(newProfileAction); deleteProfileButton->setDefaultAction(deleteProfileAction); - - for (int i = 0; i < newProfileButton->actions().size(); i++) - qDebug() << newProfileButton->actions().at(i)->objectName(); } void DataFilesPage::setupDataFiles() diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index a0360cc5ea..561f7f5d7d 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -27,13 +27,13 @@ CSVDoc::FileDialog::FileDialog(QWidget *parent) : resize(400, 400); - // connect(mDataFilesModel, SIGNAL(checkedItemsChanged(QStringList)), this, SLOT(updateOpenButton(QStringList))); - //connect(mNameLineEdit, SIGNAL(textChanged(QString)), this, SLOT(updateCreateButton(QString))); + connect(mDataFilesModel, SIGNAL(checkedItemsChanged(QStringList)), this, SLOT(updateOpenButton(QStringList))); + connect(projectNameLineEdit, SIGNAL(textChanged(QString)), this, SLOT(updateCreateButton(QString))); connect(projectCreateButton, SIGNAL(clicked()), this, SIGNAL(createNewFile())); - connect(mButtonBox, SIGNAL(accepted()), this, SIGNAL(openFiles()); - // connect(mButtonBox, SIGNAL(rejected()), this, SLOT(reject())); + connect(projectButtonBox, SIGNAL(accepted()), this, SIGNAL(openFiles())); + connect(projectButtonBox, SIGNAL(rejected()), this, SLOT(reject())); } void CSVDoc::FileDialog::updateOpenButton(const QStringList &items) diff --git a/components/esxselector/view/contentselector.cpp b/components/esxselector/view/contentselector.cpp index 0b47802416..6cba643d21 100644 --- a/components/esxselector/view/contentselector.cpp +++ b/components/esxselector/view/contentselector.cpp @@ -11,7 +11,7 @@ #include EsxView::ContentSelector::ContentSelector(QWidget *parent) : - QWidget(parent) + QDialog(parent) { setupUi(this); buildModelsAndViews(); diff --git a/components/esxselector/view/contentselector.hpp b/components/esxselector/view/contentselector.hpp index 35ef3a07c8..658e0176c6 100644 --- a/components/esxselector/view/contentselector.hpp +++ b/components/esxselector/view/contentselector.hpp @@ -1,7 +1,7 @@ #ifndef CONTENTSELECTOR_HPP #define CONTENTSELECTOR_HPP -#include +#include #include "ui_datafilespage.h" @@ -16,7 +16,7 @@ class QSortFilterProxyModel; namespace EsxView { - class ContentSelector : public QWidget, protected Ui::DataFilesPage + class ContentSelector : public QDialog, protected Ui::DataFilesPage { Q_OBJECT From a6e7cf9a8c795ffebd6c45e72f80b4650f1bbd7b Mon Sep 17 00:00:00 2001 From: graffy76 Date: Sat, 7 Sep 2013 15:57:40 -0500 Subject: [PATCH 023/434] Implementing drag and drop --- apps/opencs/view/doc/filedialog.cpp | 2 +- components/CMakeLists.txt | 3 +- components/esxselector/model/contentmodel.cpp | 428 ++++++++++++++++++ components/esxselector/model/contentmodel.hpp | 64 +++ .../esxselector/model/datafilesmodel.cpp | 216 ++++++--- .../esxselector/model/datafilesmodel.hpp | 25 +- components/esxselector/model/esmfile.cpp | 16 +- components/esxselector/model/esmfile.hpp | 5 + .../esxselector/model/masterproxymodel.cpp | 34 +- .../esxselector/model/masterproxymodel.hpp | 6 +- components/esxselector/model/modelitem.cpp | 16 +- components/esxselector/model/modelitem.hpp | 10 +- .../esxselector/model/pluginsproxymodel.cpp | 21 +- .../esxselector/model/pluginsproxymodel.hpp | 6 +- components/esxselector/model/sourcemodel.cpp | 6 + components/esxselector/model/sourcemodel.h | 18 + .../esxselector/view/contentselector.cpp | 58 ++- .../esxselector/view/contentselector.hpp | 3 + files/ui/datafilespage.ui | 63 ++- 19 files changed, 887 insertions(+), 113 deletions(-) create mode 100644 components/esxselector/model/contentmodel.cpp create mode 100644 components/esxselector/model/contentmodel.hpp create mode 100644 components/esxselector/model/sourcemodel.cpp create mode 100644 components/esxselector/model/sourcemodel.h diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index 561f7f5d7d..fb031fe5f5 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -27,7 +27,7 @@ CSVDoc::FileDialog::FileDialog(QWidget *parent) : resize(400, 400); - connect(mDataFilesModel, SIGNAL(checkedItemsChanged(QStringList)), this, SLOT(updateOpenButton(QStringList))); + // connect(mDataFilesModel, SIGNAL(checkedItemsChanged(QStringList)), this, SLOT(updateOpenButton(QStringList))); connect(projectNameLineEdit, SIGNAL(textChanged(QString)), this, SLOT(updateCreateButton(QString))); connect(projectCreateButton, SIGNAL(clicked()), this, SIGNAL(createNewFile())); diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 8b07a4e00e..0f7c5017b7 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -1,5 +1,5 @@ project (Components) - +set (CMAKE_BUILD_TYPE DEBUG) # source files add_component_dir (settings @@ -74,6 +74,7 @@ if(QT_QTGUI_LIBRARY AND QT_QTCORE_LIBRARY) add_component_qt_dir (esxselector model/masterproxymodel model/modelitem model/datafilesmodel model/pluginsproxymodel model/esmfile model/naturalsort + model/contentmodel view/profilescombobox view/comboboxlineedit view/lineedit view/contentselector ) diff --git a/components/esxselector/model/contentmodel.cpp b/components/esxselector/model/contentmodel.cpp new file mode 100644 index 0000000000..673665775a --- /dev/null +++ b/components/esxselector/model/contentmodel.cpp @@ -0,0 +1,428 @@ +#include "contentmodel.hpp" +#include "esmfile.hpp" +#include +#include +#include +#include + +EsxModel::ContentModel::ContentModel(QObject *parent) : + QAbstractTableModel(parent), mEncoding("win1252") +{} + +void EsxModel::ContentModel::setEncoding(const QString &encoding) +{ + mEncoding = encoding; +} + +int EsxModel::ContentModel::columnCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return 0; + + return 1; +} +/* +QModelIndex EsxModel::ContentModel::parent(const QModelIndex &child) const +{ + if(!child.isValid()) + return 0; + + return child.parent(); +} + +QModelIndex EsxModel::ContentModel::index(int row, int column, const QModelIndex &parent) const +{ + +} +*/ +int EsxModel::ContentModel::rowCount(const QModelIndex &parent) const +{ + if(parent.isValid()) + return 0; + + return mFiles.size(); +} + +QVariant EsxModel::ContentModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + if (index.row() >= mFiles.size()) + return QVariant(); + + EsmFile *file = item(index.row()); + + if (!file) + return QVariant(); + + const int column = index.column(); + + switch (role) + { + case Qt::EditRole: + case Qt::DisplayRole: + { + switch (column) + { + case 0: + return file->fileName(); + case 1: + return file->author(); + case 2: + return QString("%1 kB").arg(int((file->size() + 1023) / 1024)); + case 3: + return file->modified().toString(Qt::ISODate); + case 4: + return file->accessed().toString(Qt::TextDate); + case 5: + return file->version(); + case 6: + return file->path(); + case 7: + return file->masters().join(", "); + case 8: + return file->description(); + } + + return QVariant(); + } + + case Qt::TextAlignmentRole: + { + switch (column) + { + case 0: + case 1: + return Qt::AlignLeft + Qt::AlignVCenter; + case 2: + case 3: + case 4: + case 5: + return Qt::AlignRight + Qt::AlignVCenter; + default: + return Qt::AlignLeft + Qt::AlignVCenter; + } + return QVariant(); + } + + case Qt::ToolTipRole: + { + if (column != 0) + return QVariant(); + + if (file->version() == 0.0f) + return QVariant(); // Data not set + + return QString("Author: %1
\ + Version: %2
\ +
Description:
%3
\ +
Dependencies: %4
") + .arg(file->author()) + .arg(QString::number(file->version())) + .arg(file->description()) + .arg(file->masters().join(", ")); + } + + case Qt::UserRole: + { + if (file->masters().size() == 0) + return "game"; + else + return "addon"; + } + + default: + return QVariant(); + } +} + +Qt::ItemFlags EsxModel::ContentModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return Qt::NoItemFlags; + + EsmFile *file = item(index.row()); + + if (!file) + return Qt::NoItemFlags; + + Qt::ItemFlags dragDropFlags = Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; + Qt::ItemFlags checkFlags = Qt::ItemIsUserCheckable; + Qt::ItemFlags defaultFlags = Qt::ItemIsDropEnabled | Qt::ItemIsSelectable; + + if (canBeChecked(file)) + return Qt::ItemIsEnabled | dragDropFlags | checkFlags | defaultFlags; + else + return defaultFlags; +} + +bool EsxModel::ContentModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (index.isValid() && role == Qt::EditRole) + { + QString fname = value.value(); + mFiles.replace(index.row(), findItem(fname)); + emit dataChanged(index, index); + return true; + } + + return false; +} + +bool EsxModel::ContentModel::insertRows(int position, int rows, const QModelIndex &parent) +{ + beginInsertRows(parent, position, position+rows-1); + + for (int row = 0; row < rows; ++row) + mFiles.insert(position, new EsmFile); + + endInsertRows(); + return true; +} + +bool EsxModel::ContentModel::removeRows(int position, int rows, const QModelIndex &parent) +{ + beginRemoveRows(parent, position, position+rows-1); + + for (int row = 0; row < rows; ++row) + mFiles.removeAt(position); + + endRemoveRows(); + emit dataChanged(index(0,0,parent), index(rowCount()-1, 0, parent)); + return true; +} + +Qt::DropActions EsxModel::ContentModel::supportedDropActions() const +{ + return Qt::CopyAction | Qt::MoveAction; +} + +QStringList EsxModel::ContentModel::mimeTypes() const +{ + QStringList types; + types << "application/omwcontent"; + return types; +} + +QMimeData *EsxModel::ContentModel::mimeData(const QModelIndexList &indexes) const +{ + QMimeData *mimeData = new QMimeData(); + QByteArray encodedData; + + QDataStream stream(&encodedData, QIODevice::WriteOnly); + + foreach (const QModelIndex &index, indexes) + { + if (index.isValid()) + { + QString text = data(index, Qt::DisplayRole).toString(); + stream << text; + } + } + + mimeData->setData("application/omwcontent", encodedData); + return mimeData; +} + +bool EsxModel::ContentModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) +{ + if (action == Qt::IgnoreAction) + return true; + + if (!data->hasFormat("application/omwcontent")) + return false; + + if (column > 0) + return false; + + int beginRow; + + if (row != -1) + beginRow = row; + else if (parent.isValid()) + beginRow = parent.row(); + else + beginRow = rowCount(); + + QByteArray encodedData = data->data("application/omwcontent"); + QDataStream stream(&encodedData, QIODevice::ReadOnly); + QStringList newItems; + int rows = 0; + + while (!stream.atEnd()) + { + QString text; + stream >> text; + newItems << text; + ++rows; + } + + insertRows(beginRow, rows, QModelIndex()); + + foreach (const QString &text, newItems) + { + QModelIndex idx = index(beginRow, 0, QModelIndex()); + setData(idx, text); + beginRow++; + } + + return true; +} + +void EsxModel::ContentModel::addFile(EsmFile *file) +{ + emit beginInsertRows(QModelIndex(), mFiles.count(), mFiles.count()); + mFiles.append(file); + emit endInsertRows(); +} + +void EsxModel::ContentModel::addFiles(const QString &path) +{ + QDir dir(path); + QStringList filters; + filters << "*.esp" << "*.esm" << "*.omwgame" << "*.omwaddon"; + dir.setNameFilters(filters); + + // Create a decoder for non-latin characters in esx metadata + QTextCodec *codec; + + if (mEncoding == QLatin1String("win1252")) { + codec = QTextCodec::codecForName("windows-1252"); + } else if (mEncoding == QLatin1String("win1251")) { + codec = QTextCodec::codecForName("windows-1251"); + } else if (mEncoding == QLatin1String("win1250")) { + codec = QTextCodec::codecForName("windows-1250"); + } else { + return; // This should never happen; + } + + QTextDecoder *decoder = codec->makeDecoder(); + + foreach (const QString &path, dir.entryList()) { + QFileInfo info(dir.absoluteFilePath(path)); + EsmFile *file = new EsmFile(path); + + try { + ESM::ESMReader fileReader; + ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(mEncoding.toStdString())); + fileReader.setEncoder(&encoder); + fileReader.open(dir.absoluteFilePath(path).toStdString()); + + std::vector mlist = fileReader.getMasters(); + + QStringList masters; + + for (unsigned int i = 0; i < mlist.size(); ++i) { + QString master = QString::fromStdString(mlist[i].name); + masters.append(master); + } + + file->setAuthor(decoder->toUnicode(fileReader.getAuthor().c_str())); + file->setSize(info.size()); + file->setDates(info.lastModified(), info.lastRead()); + file->setVersion(fileReader.getFVer()); + file->setPath(info.absoluteFilePath()); + file->setMasters(masters); + file->setDescription(decoder->toUnicode(fileReader.getDesc().c_str())); + + + // Put the file in the table + if (findItem(path) == 0) + addFile(file); + + } catch(std::runtime_error &e) { + // An error occurred while reading the .esp + qWarning() << "Error reading addon file: " << e.what(); + continue; + } + + } + + delete decoder; +} + +EsxModel::EsmFile* EsxModel::ContentModel::findItem(const QString &name) +{ + for (int i = 0; i < mFiles.size(); ++i) + { + if (name == item(i)->fileName()) + return item(i); + } + + // Not found + return 0; +} + +EsxModel::EsmFile* EsxModel::ContentModel::item(int row) const +{ + if (row >= 0 && row < mFiles.count()) + return mFiles.at(row); + + return 0; +} + +QModelIndex EsxModel::ContentModel::indexFromItem(EsmFile *item) const +{ + if (item) + //return createIndex(mFiles.indexOf(item), 0); + return index(mFiles.indexOf(item),0); + + return QModelIndex(); +} + +Qt::CheckState EsxModel::ContentModel::checkState(const QModelIndex &index) +{ + return mCheckStates[item(index.row())->fileName()]; +} + +void EsxModel::ContentModel::setCheckState(const QModelIndex &index, Qt::CheckState state) +{ + if (!index.isValid()) + return; + + QString name = item(index.row())->fileName(); + mCheckStates[name] = state; + + // Force a redraw of the view since unchecking one item can affect another + QModelIndex firstIndex = indexFromItem(mFiles.first()); + QModelIndex lastIndex = indexFromItem(mFiles.last()); + + emit dataChanged(firstIndex, lastIndex); + //emit checkedItemsChanged(checkedItems()); + +} + +bool EsxModel::ContentModel::canBeChecked(const EsmFile *file) const +{ + //element can be checked if all its dependencies are + foreach (const QString &master, file->masters()) + { + if (!mCheckStates.contains(master) || mCheckStates[master] != Qt::Checked) + return false; + } + return true; +} + +EsxModel::ContentFileList EsxModel::ContentModel::checkedItems() const +{ + ContentFileList list; + + for (int i = 0; i < mFiles.size(); ++i) + { + EsmFile *file = item(i); + + // Only add the items that are in the checked list and available + if (mCheckStates[file->fileName()] == Qt::Checked && canBeChecked(file)) + list << file; + } + + return list; +} + +void EsxModel::ContentModel::uncheckAll() +{ + emit layoutAboutToBeChanged(); + mCheckStates.clear(); + emit layoutChanged(); +} diff --git a/components/esxselector/model/contentmodel.hpp b/components/esxselector/model/contentmodel.hpp new file mode 100644 index 0000000000..a585ab63b8 --- /dev/null +++ b/components/esxselector/model/contentmodel.hpp @@ -0,0 +1,64 @@ +#ifndef CONTENTMODEL_HPP +#define CONTENTMODEL_HPP + +#include + +namespace EsxModel +{ + class EsmFile; + + typedef QList ContentFileList; + + class ContentModel : public QAbstractTableModel + { + Q_OBJECT + public: + explicit ContentModel(QObject *parent = 0); + + void setEncoding(const QString &encoding); + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + + QVariant data(const QModelIndex &index, int role) const; + Qt::ItemFlags flags(const QModelIndex &index) const; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + + bool insertRows(int position, int rows, const QModelIndex &index = QModelIndex()); + bool removeRows(int position, int rows, const QModelIndex &index = QModelIndex()); + + Qt::DropActions supportedDropActions() const; + QStringList mimeTypes() const; + QMimeData *mimeData(const QModelIndexList &indexes) const; + bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); + + void addFiles(const QString &path); + + QModelIndex indexFromItem(EsmFile *item) const; + EsxModel::EsmFile *findItem(const QString &name); + + Qt::CheckState checkState(const QModelIndex &index); + void setCheckState(const QModelIndex &index, Qt::CheckState state); + ContentFileList checkedItems() const; + void uncheckAll(); +/* + QModelIndex index(int row, int column, const QModelIndex &parent) const; + QModelIndex parent(const QModelIndex &child) const; +*/ + private: + + void addFile(EsmFile *file); + EsmFile* item(int row) const; + bool canBeChecked(const EsmFile *file) const; + + ContentFileList mFiles; + QHash mCheckStates; + QString mEncoding; + + signals: + + public slots: + + }; +} +#endif // CONTENTMODEL_HPP diff --git a/components/esxselector/model/datafilesmodel.cpp b/components/esxselector/model/datafilesmodel.cpp index 49d0d6132a..ee940bb273 100644 --- a/components/esxselector/model/datafilesmodel.cpp +++ b/components/esxselector/model/datafilesmodel.cpp @@ -48,13 +48,12 @@ void EsxModel::DataFilesModel::setCheckState(const QModelIndex &index, Qt::Check Qt::CheckState EsxModel::DataFilesModel::checkState(const QModelIndex &index) { - EsmFile *file = item(index.row()); - return mCheckStates[file->fileName()]; + return mCheckStates[item(index.row())->fileName()]; } int EsxModel::DataFilesModel::columnCount(const QModelIndex &parent) const { - return parent.isValid() ? 0 : 9; + return parent.isValid() ? 0 : 1; } int EsxModel::DataFilesModel::rowCount(const QModelIndex &parent) const @@ -82,7 +81,7 @@ QVariant EsxModel::DataFilesModel::data(const QModelIndex &index, int role) cons if (!index.isValid()) return QVariant(); - EsmFile *file = item(index.row()); + const EsmFile *file = item(index.row()); if (!file) return QVariant(); @@ -90,6 +89,7 @@ QVariant EsxModel::DataFilesModel::data(const QModelIndex &index, int role) cons const int column = index.column(); switch (role) { + case Qt::EditRole: case Qt::DisplayRole: { switch (column) { @@ -172,25 +172,26 @@ Qt::ItemFlags EsxModel::DataFilesModel::flags(const QModelIndex &index) const if (!index.isValid()) return Qt::NoItemFlags; - EsmFile *file = item(index.row()); + const EsmFile *file = item(index.row()); + + Qt::ItemFlags dragDropFlags = Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; + Qt::ItemFlags checkFlags = Qt::ItemIsUserCheckable | Qt::ItemIsSelectable; if (!file) return Qt::NoItemFlags; - if (canBeChecked(file)) { - if (index.column() == 0) { - return Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable; - } else { - return Qt::ItemIsEnabled | Qt::ItemIsSelectable; - } - } else { - if (index.column() == 0) { - return Qt::ItemIsUserCheckable | Qt::ItemIsSelectable; - } else { - return Qt::ItemIsSelectable; - } + if (canBeChecked(file)) + { + if (index.column() == 0) + return dragDropFlags | checkFlags | Qt::ItemIsEnabled; + else + return Qt::ItemIsDropEnabled | Qt::ItemIsEnabled | Qt::ItemIsSelectable; } + if (index.column() == 0) + return dragDropFlags | checkFlags; + + return Qt::ItemIsDropEnabled | Qt::ItemIsSelectable; } QVariant EsxModel::DataFilesModel::headerData(int section, Qt::Orientation orientation, int role) const @@ -210,22 +211,27 @@ QVariant EsxModel::DataFilesModel::headerData(int section, Qt::Orientation orien case 7: return tr("Masters"); case 8: return tr("Description"); } - } /* else { - // Show row numbers - return ++section; } -*/ return QVariant(); } bool EsxModel::DataFilesModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!index.isValid()) - return false; + return false; + + if (role == Qt::EditRole) + { + qDebug() << "replacing: " << mFiles.at(index.row())->fileName(); +// mFiles.replace(index.row(), value.value()); + qDebug() << "with: " << mFiles.at(index.row())->fileName(); + emit dataChanged(index, index); + return true; + } return false; } - +//!!!!!!!!!!!!!!!!!!!!!!! bool lessThanEsmFile(const EsxModel::EsmFile *e1, const EsxModel::EsmFile *e2) { //Masters first then alphabetically @@ -236,16 +242,15 @@ bool lessThanEsmFile(const EsxModel::EsmFile *e1, const EsxModel::EsmFile *e2) return e1->fileName().toLower() < e2->fileName().toLower(); } - +//!!!!!!!!!!!!!!!!!!!!!!! bool lessThanDate(const EsxModel::EsmFile *e1, const EsxModel::EsmFile *e2) { - if (e1->modified().toString(Qt::ISODate) < e2->modified().toString(Qt::ISODate)) { + if (e1->modified().toString(Qt::ISODate) < e2->modified().toString(Qt::ISODate)) return true; - } else { + else return false; - } } - +//!!!!!!!!!!!!!!!!!!!!!!! void EsxModel::DataFilesModel::sort(int column, Qt::SortOrder order) { emit layoutAboutToBeChanged(); @@ -259,7 +264,7 @@ void EsxModel::DataFilesModel::sort(int column, Qt::SortOrder order) emit layoutChanged(); } -void EsxModel::DataFilesModel::addFile(EsmFile *file) +void EsxModel::DataFilesModel::addFile(const EsmFile *file) { emit beginInsertRows(QModelIndex(), mFiles.count(), mFiles.count()); mFiles.append(file); @@ -331,22 +336,23 @@ void EsxModel::DataFilesModel::addFiles(const QString &path) delete decoder; } -QModelIndex EsxModel::DataFilesModel::indexFromItem(EsmFile *item) const +QModelIndex EsxModel::DataFilesModel::indexFromItem(const EsmFile *item) const { if (item) - return createIndex(mFiles.indexOf(item), 0); + //return createIndex(mFiles.indexOf(item), 0); + return index(mFiles.indexOf(item),0); return QModelIndex(); } -EsxModel::EsmFile* EsxModel::DataFilesModel::findItem(const QString &name) +const EsxModel::EsmFile* EsxModel::DataFilesModel::findItem(const QString &name) { - QList::ConstIterator it; - QList::ConstIterator itEnd = mFiles.constEnd(); + EsmFileList::ConstIterator it; + EsmFileList::ConstIterator itEnd = mFiles.constEnd(); int i = 0; for (it = mFiles.constBegin(); it != itEnd; ++it) { - EsmFile *file = item(i); + const EsmFile *file = item(i); ++i; if (name == file->fileName()) @@ -357,12 +363,12 @@ EsxModel::EsmFile* EsxModel::DataFilesModel::findItem(const QString &name) return 0; } -EsxModel::EsmFile* EsxModel::DataFilesModel::item(int row) const +const EsxModel::EsmFile* EsxModel::DataFilesModel::item(int row) const { if (row >= 0 && row < mFiles.count()) return mFiles.at(row); - else - return 0; + + return 0; } EsxModel::EsmFileList EsxModel::DataFilesModel::checkedItems() @@ -372,14 +378,11 @@ EsxModel::EsmFileList EsxModel::DataFilesModel::checkedItems() EsmFileList::ConstIterator it; EsmFileList::ConstIterator itEnd = mFiles.constEnd(); - int i = 0; for (it = mFiles.constBegin(); it != itEnd; ++it) { - EsmFile *file = *it; - // Only add the items that are in the checked list and available - if (mCheckStates[file->fileName()] == Qt::Checked && canBeChecked(file)) - list << file; + if (mCheckStates[(*it)->fileName()] == Qt::Checked && canBeChecked(*it)) + list << (*it); } return list; @@ -389,12 +392,12 @@ QStringList EsxModel::DataFilesModel::checkedItemsPaths() { QStringList list; - QList::ConstIterator it; - QList::ConstIterator itEnd = mFiles.constEnd(); + EsmFileList::ConstIterator it; + EsmFileList::ConstIterator itEnd = mFiles.constEnd(); int i = 0; for (it = mFiles.constBegin(); it != itEnd; ++it) { - EsmFile *file = item(i); + const EsmFile *file = item(i); ++i; if (mCheckStates[file->fileName()] == Qt::Checked && canBeChecked(file)) @@ -403,7 +406,6 @@ QStringList EsxModel::DataFilesModel::checkedItemsPaths() return list; } - void EsxModel::DataFilesModel::uncheckAll() { emit layoutAboutToBeChanged(); @@ -411,18 +413,17 @@ void EsxModel::DataFilesModel::uncheckAll() emit layoutChanged(); } +/* EsxModel::EsmFileList EsxModel::DataFilesModel::uncheckedItems() { EsmFileList list; EsmFileList checked = checkedItems(); EsmFileList::ConstIterator it; - EsmFileList::ConstIterator itEnd = mFiles.constEnd(); - int i = 0; - for (it = mFiles.constBegin(); it != itEnd; ++it) { - EsmFile *file = item(i); - ++i; + for (it = mFiles.constBegin(); it != mFiles.constEnd(); ++it) + { + const EsmFile *file = *it; // Add the items that are not in the checked list if (!checked.contains(file)) @@ -431,8 +432,8 @@ EsxModel::EsmFileList EsxModel::DataFilesModel::uncheckedItems() return list; } - -bool EsxModel::DataFilesModel::canBeChecked(EsmFile *file) const +*/ +bool EsxModel::DataFilesModel::canBeChecked(const EsmFile *file) const { //element can be checked if all its dependencies are foreach (const QString &master, file->masters()) @@ -442,3 +443,110 @@ bool EsxModel::DataFilesModel::canBeChecked(EsmFile *file) const } return true; } + +Qt::DropActions EsxModel::DataFilesModel::supportedDropActions() const +{ + return Qt::CopyAction | Qt::MoveAction; +} + +QStringList EsxModel::DataFilesModel::mimeTypes() const +{ + QStringList types; + types << "application/omwcontent"; + return types; +} + +QMimeData *EsxModel::DataFilesModel::mimeData(const QModelIndexList &indexes) const +{ +// if (indexes.at(0).isValid()) +// return new EsmFile(*item(indexes.at(0).row())); + + return 0; +} + +bool EsxModel::DataFilesModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) +{ + if (action == Qt::IgnoreAction) + return true; + + if (action != Qt::MoveAction) + return false; + + if (!data->hasFormat("application/omwcontent")) + return false; + + int dropRow = row; + + if (dropRow == -1) + { + if (parent.isValid()) + dropRow = parent.row(); + else + dropRow = rowCount(QModelIndex()); + } + + if (parent.isValid()) + qDebug() << "parent: " << parent.data().toString(); + qDebug() << "dragged file: " << (qobject_cast(data))->fileName(); +// qDebug() << "inserting file: " << droppedfile->fileName() << " ahead of " << file->fileName(); + insertRows (dropRow, 1, QModelIndex()); + + + const EsmFile *draggedFile = qobject_cast(data); + + int dragRow = -1; + + for (int i = 0; i < mFiles.size(); ++i) + if (draggedFile->fileName() == mFiles.at(i)->fileName()) + { + dragRow = i; + break; + } + + for (int i = 0; i < mFiles.count(); ++i) + { + qDebug() << "index: " << i << "file: " << item(i)->fileName(); + qDebug() << mFiles.at(i)->fileName(); + } + + qDebug() << "drop row: " << dropRow << "; drag row: " << dragRow; +// const EsmFile *file = qobject_cast(data); + // int index = mFiles.indexOf(file); + //qDebug() << "file name: " << file->fileName() << "; index: " << index; + mFiles.swap(dropRow, dragRow); + //setData(index(startRow, 0), varFile); + emit dataChanged(index(0,0), index(rowCount(),0)); + return true; +} + +bool EsxModel::DataFilesModel::insertRows(int row, int count, const QModelIndex &parent) +{ + qDebug() << "inserting row: " << row << " count: " << count; + beginInsertRows(QModelIndex(),row, row+count-1); + + EsmFile *file = new EsmFile(); + + for (int i = 0; i < count; ++i) + mFiles.insert(row + i, file); + + endInsertRows(); + return true; +} + +bool EsxModel::DataFilesModel::removeRows(int row, int count, const QModelIndex &parent) +{ + qDebug() << "removing row: " << row << " count: " << count; + beginRemoveRows(QModelIndex(), row, row+count-1); + + for (int i = 0; i < count; ++i) + { + mFiles.removeAt(i); + } + + endRemoveRows(); + qDebug() <<"remove success"; + + emit dataChanged(parent, index(rowCount()-1, 0, parent)); + return true; +} + diff --git a/components/esxselector/model/datafilesmodel.hpp b/components/esxselector/model/datafilesmodel.hpp index 24b36aa88b..4f23cd5307 100644 --- a/components/esxselector/model/datafilesmodel.hpp +++ b/components/esxselector/model/datafilesmodel.hpp @@ -10,7 +10,7 @@ namespace EsxModel { class EsmFile; - typedef QList EsmFileList; + typedef QList EsmFileList; class DataFilesModel : public QAbstractTableModel { @@ -21,6 +21,8 @@ namespace EsxModel virtual ~DataFilesModel(); virtual int columnCount(const QModelIndex &parent = QModelIndex()) const; virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; + bool removeRows(int row, int count, const QModelIndex &parent); + bool insertRows(int row, int count, const QModelIndex &parent); bool moveRow(int oldrow, int row, const QModelIndex &parent = QModelIndex()); @@ -33,7 +35,10 @@ namespace EsxModel void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); inline QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const - { return QAbstractTableModel::index(row, column, parent); } + { + QModelIndex idx = QAbstractTableModel::index(row, 0, parent); + return idx; + } void setEncoding(const QString &encoding); @@ -41,6 +46,11 @@ namespace EsxModel void uncheckAll(); + Qt::DropActions supportedDropActions() const; + QStringList mimeTypes() const; + QMimeData *mimeData(const QModelIndexList &indexes) const; + bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); + EsmFileList checkedItems(); EsmFileList uncheckedItems(); QStringList checkedItemsPaths(); @@ -48,16 +58,17 @@ namespace EsxModel Qt::CheckState checkState(const QModelIndex &index); void setCheckState(const QModelIndex &index, Qt::CheckState state); - QModelIndex indexFromItem(EsmFile *item) const; - EsmFile* findItem(const QString &name); - EsmFile* item(int row) const; + QModelIndex indexFromItem(const EsmFile *item) const; + const EsmFile* findItem(const QString &name); + const EsmFile* item(int row) const; signals: void checkedItemsChanged(const EsmFileList &items); private: - bool canBeChecked(EsmFile *file) const; - void addFile(EsmFile *file); + + bool canBeChecked(const EsmFile *file) const; + void addFile(const EsmFile *file); EsmFileList mFiles; QHash mCheckStates; diff --git a/components/esxselector/model/esmfile.cpp b/components/esxselector/model/esmfile.cpp index 96b90e44e2..95cf703121 100644 --- a/components/esxselector/model/esmfile.cpp +++ b/components/esxselector/model/esmfile.cpp @@ -1,13 +1,17 @@ #include "esmfile.hpp" EsxModel::EsmFile::EsmFile(QString fileName, ModelItem *parent) - : ModelItem(parent) -{ - mFileName = fileName; - mSize = 0; - mVersion = 0.0f; -} + : ModelItem(parent), mFileName(fileName), mSize(0), mVersion(0.0f) +{} +/* +EsxModel::EsmFile::EsmFile(const EsmFile &file) + : ModelItem(file.parent()), mFileName(file.mFileName), mSize(file.mSize), + mVersion(file.mVersion), mAuthor(file.mAuthor), mModified(file.mModified), + mAccessed(file.mAccessed), mPath(file.mPath), mMasters(file.mMasters), + mDescription(file.mDescription) +{} +*/ void EsxModel::EsmFile::setFileName(const QString &fileName) { mFileName = fileName; diff --git a/components/esxselector/model/esmfile.hpp b/components/esxselector/model/esmfile.hpp index 6a3e36b538..0cda018b3c 100644 --- a/components/esxselector/model/esmfile.hpp +++ b/components/esxselector/model/esmfile.hpp @@ -14,7 +14,9 @@ namespace EsxModel Q_PROPERTY(QString filename READ fileName) public: + EsmFile(QString fileName = QString(), ModelItem *parent = 0); + // EsmFile(const EsmFile &); ~EsmFile() {} @@ -38,6 +40,7 @@ namespace EsxModel inline QStringList masters() const { return mMasters; } inline QString description() const { return mDescription; } + //inline ModelItem *parent() const { return ModelItem::parent(); this->} private: QString mFileName; @@ -53,4 +56,6 @@ namespace EsxModel }; } +Q_DECLARE_METATYPE (EsxModel::EsmFile *) + #endif diff --git a/components/esxselector/model/masterproxymodel.cpp b/components/esxselector/model/masterproxymodel.cpp index 011e5ebd54..46d68ca513 100644 --- a/components/esxselector/model/masterproxymodel.cpp +++ b/components/esxselector/model/masterproxymodel.cpp @@ -1,4 +1,6 @@ #include "masterproxymodel.hpp" +#include +#include EsxModel::MasterProxyModel::MasterProxyModel(QObject *parent, QAbstractTableModel* model) : QSortFilterProxyModel(parent) @@ -7,10 +9,36 @@ EsxModel::MasterProxyModel::MasterProxyModel(QObject *parent, QAbstractTableMode setFilterRole (Qt::UserRole); if (model) - setSourceModel (model); + setSourceModel (model); + //connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(slotSourceModelChanged(QModelIndex, QModelIndex))); } - +/* QVariant EsxModel::MasterProxyModel::data(const QModelIndex &index, int role) const { - return QSortFilterProxyModel::data (index, role); + if (index.isValid()) + return QSortFilterProxyModel::data (index, role); + + return 0; +} +*/ +void EsxModel::MasterProxyModel::slotSourceModelChanged(QModelIndex topLeft, QModelIndex botRight) +{ + qDebug() << "source data changed.. updating master proxy"; + emit dataChanged(index(0,0), index(rowCount()-1,0)); + + int curRow = -1; +/* + for (int i = 0; i < rowCount() - 1; ++i) + { + if (index(i,0).data(Qt::CheckState) == Qt::Checked) + { + curRow = i; + break; + } + } + + reset(); +*/ + if (curRow != -1); + // index(curRow, 0).setDataQt::CheckState) } diff --git a/components/esxselector/model/masterproxymodel.hpp b/components/esxselector/model/masterproxymodel.hpp index fed01bdb14..8a5c730325 100644 --- a/components/esxselector/model/masterproxymodel.hpp +++ b/components/esxselector/model/masterproxymodel.hpp @@ -2,6 +2,8 @@ #define MASTERPROXYMODEL_HPP #include +#include +#include class QAbstractTableModel; @@ -12,12 +14,12 @@ namespace EsxModel Q_OBJECT public: explicit MasterProxyModel(QObject *parent = 0, QAbstractTableModel *model = 0); - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + // virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; signals: public slots: - + void slotSourceModelChanged(QModelIndex topLeft, QModelIndex botRight); }; } #endif // MASTERPROXYMODEL_HPP diff --git a/components/esxselector/model/modelitem.cpp b/components/esxselector/model/modelitem.cpp index 8c1e83695d..03b19f6910 100644 --- a/components/esxselector/model/modelitem.cpp +++ b/components/esxselector/model/modelitem.cpp @@ -2,9 +2,14 @@ EsxModel::ModelItem::ModelItem(ModelItem *parent) : mParentItem(parent) - , QObject(parent) { } +/* +EsxModel::ModelItem::ModelItem(const ModelItem *parent) + // : mParentItem(parent) +{ +} +*/ EsxModel::ModelItem::~ModelItem() { @@ -12,11 +17,18 @@ EsxModel::ModelItem::~ModelItem() } -EsxModel::ModelItem *EsxModel::ModelItem::parent() +EsxModel::ModelItem *EsxModel::ModelItem::parent() const { return mParentItem; } +bool EsxModel::ModelItem::hasFormat(const QString &mimetype) const +{ + if (mimetype == "application/omwcontent") + return true; + + return QMimeData::hasFormat(mimetype); +} int EsxModel::ModelItem::row() const { if (mParentItem) diff --git a/components/esxselector/model/modelitem.hpp b/components/esxselector/model/modelitem.hpp index 64596302c8..5ee5e417ed 100644 --- a/components/esxselector/model/modelitem.hpp +++ b/components/esxselector/model/modelitem.hpp @@ -1,20 +1,22 @@ #ifndef MODELITEM_HPP #define MODELITEM_HPP -#include +#include #include namespace EsxModel { - class ModelItem : public QObject + class ModelItem : public QMimeData { Q_OBJECT public: ModelItem(ModelItem *parent = 0); + //ModelItem(const ModelItem *parent = 0); + ~ModelItem(); - ModelItem *parent(); + ModelItem *parent() const; int row() const; int childCount() const; @@ -24,6 +26,8 @@ namespace EsxModel void appendChild(ModelItem *child); void removeChild(int row); + bool hasFormat(const QString &mimetype) const; + //virtual bool acceptChild(ModelItem *child); protected: diff --git a/components/esxselector/model/pluginsproxymodel.cpp b/components/esxselector/model/pluginsproxymodel.cpp index 4fde11f470..412367b649 100644 --- a/components/esxselector/model/pluginsproxymodel.cpp +++ b/components/esxselector/model/pluginsproxymodel.cpp @@ -1,7 +1,8 @@ #include "pluginsproxymodel.hpp" -#include "datafilesmodel.hpp" +#include "contentmodel.hpp" +#include -EsxModel::PluginsProxyModel::PluginsProxyModel(QObject *parent, DataFilesModel *model) : +EsxModel::PluginsProxyModel::PluginsProxyModel(QObject *parent, ContentModel *model) : QSortFilterProxyModel(parent) { setFilterRegExp (QString("addon")); @@ -22,12 +23,18 @@ QVariant EsxModel::PluginsProxyModel::data(const QModelIndex &index, int role) c { case Qt::CheckStateRole: { - if (index.column() != 0) + if (index.column() != 0) return QVariant(); - return static_cast(sourceModel())->checkState(mapToSource(index)); + return static_cast(sourceModel())->checkState(mapToSource(index)); } - }; - - return QSortFilterProxyModel::data (index, role); + } + return QSortFilterProxyModel::data(index, role); +} + +bool EsxModel::PluginsProxyModel::removeRows(int position, int rows, const QModelIndex &parent) +{ + bool success = QSortFilterProxyModel::removeRows(position, rows, parent); + + return success; } diff --git a/components/esxselector/model/pluginsproxymodel.hpp b/components/esxselector/model/pluginsproxymodel.hpp index 04c18c2e22..4415df716e 100644 --- a/components/esxselector/model/pluginsproxymodel.hpp +++ b/components/esxselector/model/pluginsproxymodel.hpp @@ -8,7 +8,7 @@ class QAbstractTableModel; namespace EsxModel { - class DataFilesModel; + class ContentModel; class PluginsProxyModel : public QSortFilterProxyModel { @@ -16,10 +16,12 @@ namespace EsxModel public: - explicit PluginsProxyModel(QObject *parent = 0, DataFilesModel *model = 0); + explicit PluginsProxyModel(QObject *parent = 0, ContentModel *model = 0); ~PluginsProxyModel(); virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + + bool removeRows(int row, int count, const QModelIndex &parent); }; } diff --git a/components/esxselector/model/sourcemodel.cpp b/components/esxselector/model/sourcemodel.cpp new file mode 100644 index 0000000000..7b54adba91 --- /dev/null +++ b/components/esxselector/model/sourcemodel.cpp @@ -0,0 +1,6 @@ +#include "sourcemodel.h" + +SourceModel::SourceModel(QObject *parent) : + QAbstractTableClass(parent) +{ +} diff --git a/components/esxselector/model/sourcemodel.h b/components/esxselector/model/sourcemodel.h new file mode 100644 index 0000000000..cf51456755 --- /dev/null +++ b/components/esxselector/model/sourcemodel.h @@ -0,0 +1,18 @@ +#ifndef SOURCEMODEL_H +#define SOURCEMODEL_H + +#include + +class SourceModel : public QAbstractTableClass +{ + Q_OBJECT +public: + explicit SourceModel(QObject *parent = 0); + +signals: + +public slots: + +}; + +#endif // SOURCEMODEL_H diff --git a/components/esxselector/view/contentselector.cpp b/components/esxselector/view/contentselector.cpp index 6cba643d21..bc7cc2b8bb 100644 --- a/components/esxselector/view/contentselector.cpp +++ b/components/esxselector/view/contentselector.cpp @@ -3,6 +3,8 @@ #include "../model/datafilesmodel.hpp" #include "../model/masterproxymodel.hpp" #include "../model/pluginsproxymodel.hpp" +#include "../model/contentmodel.hpp" +#include "../model/esmfile.hpp" #include @@ -14,7 +16,33 @@ EsxView::ContentSelector::ContentSelector(QWidget *parent) : QDialog(parent) { setupUi(this); - buildModelsAndViews(); + // buildModelsAndViews(); + buildDragDropModelView(); +} +void EsxView::ContentSelector::buildDragDropModelView() +{ + mContentModel = new EsxModel::ContentModel(); + + //mContentModel->addFiles("/home/joel/Projects/OpenMW/Data_Files"); + mMasterProxyModel = new EsxModel::MasterProxyModel(this, mContentModel); + mPluginsProxyModel = new EsxModel::PluginsProxyModel(this, mContentModel); + + tableView->setModel (mPluginsProxyModel); + + masterView->setPlaceholderText(QString("Select a game file...")); + masterView->setModel(mMasterProxyModel); + pluginView->setModel(mPluginsProxyModel); + + profilesComboBox->setPlaceholderText(QString("Select a profile...")); + + updateViews(); + connect(pluginView, SIGNAL(clicked(const QModelIndex &)), this, SLOT(slotPluginTableItemClicked(const QModelIndex &))); + connect(masterView, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCurrentMasterIndexChanged(int))); + connect(profilesComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCurrentProfileIndexChanged(int))); + + + connect(mContentModel, SIGNAL(layoutChanged()), this, SLOT(updateViews())); + connect(tableView, SIGNAL(clicked(const QModelIndex &)), this, SLOT(slotPluginTableItemClicked(const QModelIndex &))); } void EsxView::ContentSelector::buildModelsAndViews() @@ -22,15 +50,15 @@ void EsxView::ContentSelector::buildModelsAndViews() // Models mDataFilesModel = new EsxModel::DataFilesModel (this); - mMasterProxyModel = new EsxModel::MasterProxyModel (this, mDataFilesModel); - mPluginsProxyModel = new EsxModel::PluginsProxyModel (this, mDataFilesModel); + // mMasterProxyModel = new EsxModel::MasterProxyModel (this, mDataFilesModel); + // mPluginsProxyModel = new EsxModel::PluginsProxyModel (this, mDataFilesModel); masterView->setPlaceholderText(QString("Select a game file...")); masterView->setModel(mMasterProxyModel); pluginView->setModel(mPluginsProxyModel); profilesComboBox->setPlaceholderText(QString("Select a profile...")); - + updateViews(); connect(mDataFilesModel, SIGNAL(layoutChanged()), this, SLOT(updateViews())); connect(pluginView, SIGNAL(clicked(const QModelIndex &)), this, SLOT(slotPluginTableItemClicked(const QModelIndex &))); connect(masterView, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCurrentMasterIndexChanged(int))); @@ -39,15 +67,15 @@ void EsxView::ContentSelector::buildModelsAndViews() void EsxView::ContentSelector::addFiles(const QString &path) { - mDataFilesModel->addFiles(path); - mDataFilesModel->sort(3); // Sort by date accessed + mContentModel->addFiles(path); + mContentModel->sort(3); // Sort by date accessed masterView->setCurrentIndex(-1); - mDataFilesModel->uncheckAll(); + mContentModel->uncheckAll(); } void EsxView::ContentSelector::setEncoding(const QString &encoding) { - mDataFilesModel->setEncoding(encoding); + mContentModel->setEncoding(encoding); } void EsxView::ContentSelector::setCheckState(QModelIndex index, QSortFilterProxyModel *model) @@ -62,15 +90,20 @@ void EsxView::ContentSelector::setCheckState(QModelIndex index, QSortFilterProxy if (sourceIndex.isValid()) { - (mDataFilesModel->checkState(sourceIndex) == Qt::Checked) - ? mDataFilesModel->setCheckState(sourceIndex, Qt::Unchecked) - : mDataFilesModel->setCheckState(sourceIndex, Qt::Checked); + (mContentModel->checkState(sourceIndex) == Qt::Checked) + ? mContentModel->setCheckState(sourceIndex, Qt::Unchecked) + : mContentModel->setCheckState(sourceIndex, Qt::Checked); } } QStringList EsxView::ContentSelector::checkedItemsPaths() { - return mDataFilesModel->checkedItemsPaths(); + QStringList itemPaths; + + foreach( const EsxModel::EsmFile *file, mContentModel->checkedItems()) + itemPaths << file->path(); + + return itemPaths; } void EsxView::ContentSelector::updateViews() @@ -106,5 +139,6 @@ void EsxView::ContentSelector::slotCurrentMasterIndexChanged(int index) void EsxView::ContentSelector::slotPluginTableItemClicked(const QModelIndex &index) { + qDebug() << "setting checkstate in plugin..."; setCheckState(index, mPluginsProxyModel); } diff --git a/components/esxselector/view/contentselector.hpp b/components/esxselector/view/contentselector.hpp index 658e0176c6..06cc8f3f0b 100644 --- a/components/esxselector/view/contentselector.hpp +++ b/components/esxselector/view/contentselector.hpp @@ -7,6 +7,7 @@ namespace EsxModel { + class ContentModel; class DataFilesModel; class PluginsProxyModel; class MasterProxyModel; @@ -23,6 +24,7 @@ namespace EsxView protected: EsxModel::DataFilesModel *mDataFilesModel; + EsxModel::ContentModel *mContentModel; EsxModel::MasterProxyModel *mMasterProxyModel; EsxModel::PluginsProxyModel *mPluginsProxyModel; @@ -37,6 +39,7 @@ namespace EsxView void setCheckState(QModelIndex index, QSortFilterProxyModel *model); QStringList checkedItemsPaths(); void on_checkAction_triggered(); + void buildDragDropModelView(); signals: void profileChanged(int index); diff --git a/files/ui/datafilespage.ui b/files/ui/datafilespage.ui index 60e8b8bf5d..76689627b0 100644 --- a/files/ui/datafilespage.ui +++ b/files/ui/datafilespage.ui @@ -7,28 +7,19 @@ 0 0 518 - 304 + 310
Qt::DefaultContextMenu - + Content - - - 3 - - - 6 - - - 6 - + @@ -41,7 +32,7 @@ - + @@ -56,6 +47,18 @@ QAbstractItemView::NoEditTriggers + + true + + + false + + + QAbstractItemView::DragDrop + + + Qt::MoveAction + true @@ -82,6 +85,40 @@ + + + + QAbstractItemView::NoEditTriggers + + + true + + + false + + + QAbstractItemView::DragDrop + + + Qt::MoveAction + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + false + + + false + + + false + + + From 46a734852b5b6a36ddb554cb86d297c2bd19e71a Mon Sep 17 00:00:00 2001 From: gus Date: Tue, 10 Sep 2013 16:16:13 +0200 Subject: [PATCH 024/434] adding script instruction getLOS + some test about AI --- apps/openmw/mwbase/world.hpp | 3 ++ apps/openmw/mwmechanics/aisequence.cpp | 51 ++++++++++++++++++++++++++ apps/openmw/mwscript/aiextensions.cpp | 28 ++++++++++++++ apps/openmw/mwscript/docs/vmformat.txt | 2 + apps/openmw/mwworld/worldimp.cpp | 5 +++ apps/openmw/mwworld/worldimp.hpp | 3 ++ components/compiler/extensions0.cpp | 1 + components/compiler/opcodes.hpp | 2 + 8 files changed, 95 insertions(+) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 6ca900a4d3..071d2a16c4 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -356,6 +356,9 @@ namespace MWBase virtual void getItemsOwnedBy (const MWWorld::Ptr& npc, std::vector& out) = 0; ///< get all items in active cells owned by this Npc + virtual bool getLOS(const MWWorld::Ptr& npc,const MWWorld::Ptr& targetNpc) = 0; + ///< get Line of Sight (morrowind stupid implementation) + virtual void enableActorCollision(const MWWorld::Ptr& actor, bool enable) = 0; virtual void setupExternalRendering (MWRender::ExternalRendering& rendering) = 0; diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 2f06b849a3..79ecf1586c 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -9,6 +9,13 @@ #include "aifollow.hpp" #include "aiactivate.hpp" +#include "..\mwworld\class.hpp" +#include "creaturestats.hpp" +#include "npcstats.hpp" +#include "..\mwbase\environment.hpp" +#include "..\mwbase\world.hpp" +#include "..\mwworld\player.hpp" + void MWMechanics::AiSequence::copy (const AiSequence& sequence) { for (std::list::const_iterator iter (sequence.mPackages.begin()); @@ -54,6 +61,50 @@ bool MWMechanics::AiSequence::isPackageDone() const void MWMechanics::AiSequence::execute (const MWWorld::Ptr& actor) { + /*if(actor != MWBase::Environment::get().getWorld()->getPlayer().getPlayer()) + { + MWMechanics::DrawState_ state = MWWorld::Class::get(actor).getNpcStats(actor).getDrawState(); + if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing) + MWWorld::Class::get(actor).getNpcStats(actor).setDrawState(MWMechanics::DrawState_Weapon); + MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(true); + + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + ESM::Position pos = actor.getRefData().getPosition(); + const ESM::Pathgrid *pathgrid = + MWBase::Environment::get().getWorld()->getStore().get().search(*actor.getCell()->mCell); + + int cellX = actor.getCell()->mCell->mData.mX; + int cellY = actor.getCell()->mCell->mData.mY; + float xCell = 0; + float yCell = 0; + + if (actor.getCell()->mCell->isExterior()) + { + xCell = actor.getCell()->mCell->mData.mX * ESM::Land::REAL_SIZE; + yCell = actor.getCell()->mCell->mData.mY * ESM::Land::REAL_SIZE; + } + + ESM::Pathgrid::Point dest; + dest.mX = player.getRefData().getPosition().pos[0]; + dest.mY = player.getRefData().getPosition().pos[1]; + dest.mZ = player.getRefData().getPosition().pos[2]; + + ESM::Pathgrid::Point start; + start.mX = pos.pos[0]; + start.mY = pos.pos[1]; + start.mZ = pos.pos[2]; + + PathFinder mPathFinder; + mPathFinder.buildPath(start, dest, pathgrid, xCell, yCell, true); + float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); + MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false); + MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; + + if(dest.mX - start.mX < 100) + { + MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(false); + } + }*/ if (!mPackages.empty()) { if (mPackages.front()->execute (actor)) diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index fac44c08f3..39ed36ac7c 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -17,6 +17,9 @@ #include "../mwmechanics/aitravel.hpp" #include "../mwmechanics/aiwander.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + #include "interpretercontext.hpp" #include "ref.hpp" @@ -364,6 +367,28 @@ namespace MWScript } }; + template + class OpGetLineOfSight : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + + MWWorld::Ptr source = R()(runtime); + + std::string actorID = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + MWWorld::Ptr dest = MWBase::Environment::get().getWorld()->getPtr(actorID,true); + bool value = false; + if(dest != MWWorld::Ptr() ) + { + value = MWBase::Environment::get().getWorld()->getLOS(source,dest); + } + runtime.push (value); + } + }; void installOpcodes (Interpreter::Interpreter& interpreter) { @@ -389,6 +414,9 @@ namespace MWScript interpreter.installSegment5 (Compiler::Ai::opcodeGetCurrentAiPackageExplicit, new OpGetCurrentAIPackage); interpreter.installSegment3 (Compiler::Ai::opcodeGetDetected, new OpGetDetected); interpreter.installSegment3 (Compiler::Ai::opcodeGetDetectedExplicit, new OpGetDetected); + interpreter.installSegment5 (Compiler::Ai::opcodeGetLineOfSight, new OpGetLineOfSight); + interpreter.installSegment5 (Compiler::Ai::opcodeGetLineOfSightExplicit, new OpGetLineOfSight); + interpreter.installSegment5 (Compiler::Ai::opcodeSetHello, new OpSetAiSetting(0)); interpreter.installSegment5 (Compiler::Ai::opcodeSetHelloExplicit, new OpSetAiSetting(0)); interpreter.installSegment5 (Compiler::Ai::opcodeSetFight, new OpSetAiSetting(1)); diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index 08b4991753..053964effd 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -351,5 +351,7 @@ op 0x200021b: SetWerewolfAcrobatics op 0x200021c: SetWerewolfAcrobaticsExplicit op 0x200021d: ShowVars op 0x200021e: ShowVarsExplicit +op 0x200021f: GetLineOfSight +op 0x200022a: GetLineOfSightExplicit opcodes 0x200021f-0x3ffffff unused diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 09dc0493e8..5e293a2922 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1779,6 +1779,11 @@ namespace MWWorld } } + bool World::getLOS(const MWWorld::Ptr& npc,const MWWorld::Ptr& targetNpc) + { + return false; + } + void World::enableActorCollision(const MWWorld::Ptr& actor, bool enable) { OEngine::Physic::PhysicActor *physicActor = mPhysEngine->getCharacter(actor.getRefData().getHandle()); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 71391239b3..ac1e0b3833 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -403,6 +403,9 @@ namespace MWWorld virtual void getItemsOwnedBy (const MWWorld::Ptr& npc, std::vector& out); ///< get all items in active cells owned by this Npc + virtual bool getLOS(const MWWorld::Ptr& npc,const MWWorld::Ptr& targetNpc); + ///< get Line of Sight (morrowind stupid implementation) + virtual void enableActorCollision(const MWWorld::Ptr& actor, bool enable); virtual void setupExternalRendering (MWRender::ExternalRendering& rendering); diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index 9e0c36825a..a671420d43 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -63,6 +63,7 @@ namespace Compiler extensions.registerFunction ("getfight", 'l', "", opcodeGetFight, opcodeGetFightExplicit); extensions.registerFunction ("getflee", 'l', "", opcodeGetFlee, opcodeGetFleeExplicit); extensions.registerFunction ("getalarm", 'l', "", opcodeGetAlarm, opcodeGetAlarmExplicit); + extensions.registerFunction ("getlineofsight", 'l', "c", opcodeGetLineOfSight, opcodeGetLineOfSightExplicit); } } diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp index c4e2c1bc6b..185eb3c219 100644 --- a/components/compiler/opcodes.hpp +++ b/components/compiler/opcodes.hpp @@ -49,6 +49,8 @@ namespace Compiler const int opcodeGetFleeExplicit = 0x20001c4; const int opcodeGetAlarm = 0x20001c5; const int opcodeGetAlarmExplicit = 0x20001c6; + const int opcodeGetLineOfSight = 0x200021f; + const int opcodeGetLineOfSightExplicit = 0x200022a; } namespace Animation From c961abce963d4d9681861510dad796c0fb65dd7a Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 14 Sep 2013 14:00:07 +0200 Subject: [PATCH 025/434] added warning message to startup window --- apps/opencs/view/doc/startup.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/apps/opencs/view/doc/startup.cpp b/apps/opencs/view/doc/startup.cpp index 4cc64f2dfa..5d59492c64 100644 --- a/apps/opencs/view/doc/startup.cpp +++ b/apps/opencs/view/doc/startup.cpp @@ -104,6 +104,17 @@ CSVDoc::StartupDialogue::StartupDialogue() : mWidth (0), mColumn (2) layout->addWidget (createButtons()); layout->addWidget (createTools()); + /// \todo remove this label once loading and saving are fully implemented + QLabel *warning = new QLabel ("WARNING:

OpenCS is in alpha stage.
The code for loading and saving is incomplete.
This version of OpenCS is only a preview.
Do NOT use it for real editing!
You will lose records both on loading and on saving.

Please note:
If you lose data and come to the OpenMW forum to complain,
we will mock you.
"); + + QFont font; + font.setPointSize (12); + font.setBold (true); + + warning->setFont (font); + + layout->addWidget (warning, 1); + setLayout (layout); QRect scr = QApplication::desktop()->screenGeometry(); From 077a157841a59bf8bb3ed7e4e7dbfd5bf745b9ff Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 14 Sep 2013 14:56:23 +0200 Subject: [PATCH 026/434] moved Operation and Stage from model/tools to model/doc --- apps/opencs/CMakeLists.txt | 8 ++++---- apps/opencs/model/{tools => doc}/operation.cpp | 17 ++++++++--------- apps/opencs/model/{tools => doc}/operation.hpp | 6 +++--- apps/opencs/model/doc/stage.cpp | 4 ++++ apps/opencs/model/{tools => doc}/stage.hpp | 8 ++++---- apps/opencs/model/tools/birthsigncheck.hpp | 4 ++-- apps/opencs/model/tools/classcheck.hpp | 4 ++-- apps/opencs/model/tools/factioncheck.hpp | 4 ++-- apps/opencs/model/tools/mandatoryid.hpp | 4 ++-- apps/opencs/model/tools/racecheck.hpp | 4 ++-- apps/opencs/model/tools/regioncheck.hpp | 4 ++-- apps/opencs/model/tools/skillcheck.hpp | 4 ++-- apps/opencs/model/tools/soundcheck.hpp | 4 ++-- apps/opencs/model/tools/spellcheck.hpp | 4 ++-- apps/opencs/model/tools/stage.cpp | 4 ---- apps/opencs/model/tools/tools.cpp | 8 ++++---- apps/opencs/model/tools/tools.hpp | 10 +++++++--- apps/opencs/model/tools/verifier.hpp | 4 ++-- 18 files changed, 54 insertions(+), 51 deletions(-) rename apps/opencs/model/{tools => doc}/operation.cpp (82%) rename apps/opencs/model/{tools => doc}/operation.hpp (92%) create mode 100644 apps/opencs/model/doc/stage.cpp rename apps/opencs/model/{tools => doc}/stage.hpp (65%) delete mode 100644 apps/opencs/model/tools/stage.cpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index aa6f6ba766..367c43eb73 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -5,11 +5,11 @@ opencs_units (. editor) set (CMAKE_BUILD_TYPE DEBUG) opencs_units (model/doc - document + document operation ) opencs_units_noqt (model/doc - documentmanager + documentmanager stage ) opencs_hdrs_noqt (model/doc @@ -33,11 +33,11 @@ opencs_hdrs_noqt (model/world opencs_units (model/tools - tools operation reportmodel + tools reportmodel ) opencs_units_noqt (model/tools - stage verifier mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck + verifier mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck birthsigncheck spellcheck ) diff --git a/apps/opencs/model/tools/operation.cpp b/apps/opencs/model/doc/operation.cpp similarity index 82% rename from apps/opencs/model/tools/operation.cpp rename to apps/opencs/model/doc/operation.cpp index 71761cdaea..8f7472c5f6 100644 --- a/apps/opencs/model/tools/operation.cpp +++ b/apps/opencs/model/doc/operation.cpp @@ -6,11 +6,10 @@ #include -#include "../doc/state.hpp" - +#include "state.hpp" #include "stage.hpp" -void CSMTools::Operation::prepareStages() +void CSMDoc::Operation::prepareStages() { mCurrentStage = mStages.begin(); mCurrentStep = 0; @@ -24,15 +23,15 @@ void CSMTools::Operation::prepareStages() } } -CSMTools::Operation::Operation (int type) : mType (type) {} +CSMDoc::Operation::Operation (int type) : mType (type) {} -CSMTools::Operation::~Operation() +CSMDoc::Operation::~Operation() { for (std::vector >::iterator iter (mStages.begin()); iter!=mStages.end(); ++iter) delete iter->first; } -void CSMTools::Operation::run() +void CSMDoc::Operation::run() { prepareStages(); @@ -45,17 +44,17 @@ void CSMTools::Operation::run() exec(); } -void CSMTools::Operation::appendStage (Stage *stage) +void CSMDoc::Operation::appendStage (Stage *stage) { mStages.push_back (std::make_pair (stage, 0)); } -void CSMTools::Operation::abort() +void CSMDoc::Operation::abort() { exit(); } -void CSMTools::Operation::verify() +void CSMDoc::Operation::verify() { std::vector messages; diff --git a/apps/opencs/model/tools/operation.hpp b/apps/opencs/model/doc/operation.hpp similarity index 92% rename from apps/opencs/model/tools/operation.hpp rename to apps/opencs/model/doc/operation.hpp index 4731c58fa8..703098852f 100644 --- a/apps/opencs/model/tools/operation.hpp +++ b/apps/opencs/model/doc/operation.hpp @@ -1,11 +1,11 @@ -#ifndef CSM_TOOLS_OPERATION_H -#define CSM_TOOLS_OPERATION_H +#ifndef CSM_DOC_OPERATION_H +#define CSM_DOC_OPERATION_H #include #include -namespace CSMTools +namespace CSMDoc { class Stage; diff --git a/apps/opencs/model/doc/stage.cpp b/apps/opencs/model/doc/stage.cpp new file mode 100644 index 0000000000..99b7657709 --- /dev/null +++ b/apps/opencs/model/doc/stage.cpp @@ -0,0 +1,4 @@ + +#include "stage.hpp" + +CSMDoc::Stage::~Stage() {} \ No newline at end of file diff --git a/apps/opencs/model/tools/stage.hpp b/apps/opencs/model/doc/stage.hpp similarity index 65% rename from apps/opencs/model/tools/stage.hpp rename to apps/opencs/model/doc/stage.hpp index 3020936f32..1f96c60b43 100644 --- a/apps/opencs/model/tools/stage.hpp +++ b/apps/opencs/model/doc/stage.hpp @@ -1,10 +1,10 @@ -#ifndef CSM_TOOLS_STAGE_H -#define CSM_TOOLS_STAGE_H +#ifndef CSM_DOC_STAGE_H +#define CSM_DOC_STAGE_H #include #include -namespace CSMTools +namespace CSMDoc { class Stage { @@ -16,7 +16,7 @@ namespace CSMTools ///< \return number of steps virtual void perform (int stage, std::vector& messages) = 0; - ///< Messages resulting from this tage will be appended to \a messages. + ///< Messages resulting from this stage will be appended to \a messages. }; } diff --git a/apps/opencs/model/tools/birthsigncheck.hpp b/apps/opencs/model/tools/birthsigncheck.hpp index 42b5a6b244..bdd65b44ab 100644 --- a/apps/opencs/model/tools/birthsigncheck.hpp +++ b/apps/opencs/model/tools/birthsigncheck.hpp @@ -5,12 +5,12 @@ #include "../world/idcollection.hpp" -#include "stage.hpp" +#include "../doc/stage.hpp" namespace CSMTools { /// \brief VerifyStage: make sure that birthsign records are internally consistent - class BirthsignCheckStage : public Stage + class BirthsignCheckStage : public CSMDoc::Stage { const CSMWorld::IdCollection& mBirthsigns; diff --git a/apps/opencs/model/tools/classcheck.hpp b/apps/opencs/model/tools/classcheck.hpp index a29d7c8b78..3604b451c5 100644 --- a/apps/opencs/model/tools/classcheck.hpp +++ b/apps/opencs/model/tools/classcheck.hpp @@ -5,12 +5,12 @@ #include "../world/idcollection.hpp" -#include "stage.hpp" +#include "../doc/stage.hpp" namespace CSMTools { /// \brief VerifyStage: make sure that class records are internally consistent - class ClassCheckStage : public Stage + class ClassCheckStage : public CSMDoc::Stage { const CSMWorld::IdCollection& mClasses; diff --git a/apps/opencs/model/tools/factioncheck.hpp b/apps/opencs/model/tools/factioncheck.hpp index 8686505727..7cd80347db 100644 --- a/apps/opencs/model/tools/factioncheck.hpp +++ b/apps/opencs/model/tools/factioncheck.hpp @@ -5,12 +5,12 @@ #include "../world/idcollection.hpp" -#include "stage.hpp" +#include "../doc/stage.hpp" namespace CSMTools { /// \brief VerifyStage: make sure that faction records are internally consistent - class FactionCheckStage : public Stage + class FactionCheckStage : public CSMDoc::Stage { const CSMWorld::IdCollection& mFactions; diff --git a/apps/opencs/model/tools/mandatoryid.hpp b/apps/opencs/model/tools/mandatoryid.hpp index 342e2d7540..5fddf08d32 100644 --- a/apps/opencs/model/tools/mandatoryid.hpp +++ b/apps/opencs/model/tools/mandatoryid.hpp @@ -6,7 +6,7 @@ #include "../world/universalid.hpp" -#include "stage.hpp" +#include "../doc/stage.hpp" namespace CSMWorld { @@ -16,7 +16,7 @@ namespace CSMWorld namespace CSMTools { /// \brief Verify stage: make sure that records with specific IDs exist. - class MandatoryIdStage : public Stage + class MandatoryIdStage : public CSMDoc::Stage { const CSMWorld::CollectionBase& mIdCollection; CSMWorld::UniversalId mCollectionId; diff --git a/apps/opencs/model/tools/racecheck.hpp b/apps/opencs/model/tools/racecheck.hpp index 155f799021..ff9948bf6d 100644 --- a/apps/opencs/model/tools/racecheck.hpp +++ b/apps/opencs/model/tools/racecheck.hpp @@ -5,12 +5,12 @@ #include "../world/idcollection.hpp" -#include "stage.hpp" +#include "../doc/stage.hpp" namespace CSMTools { /// \brief VerifyStage: make sure that race records are internally consistent - class RaceCheckStage : public Stage + class RaceCheckStage : public CSMDoc::Stage { const CSMWorld::IdCollection& mRaces; bool mPlayable; diff --git a/apps/opencs/model/tools/regioncheck.hpp b/apps/opencs/model/tools/regioncheck.hpp index b421356514..c8c437cbd2 100644 --- a/apps/opencs/model/tools/regioncheck.hpp +++ b/apps/opencs/model/tools/regioncheck.hpp @@ -5,12 +5,12 @@ #include "../world/idcollection.hpp" -#include "stage.hpp" +#include "../doc/stage.hpp" namespace CSMTools { /// \brief VerifyStage: make sure that region records are internally consistent - class RegionCheckStage : public Stage + class RegionCheckStage : public CSMDoc::Stage { const CSMWorld::IdCollection& mRegions; diff --git a/apps/opencs/model/tools/skillcheck.hpp b/apps/opencs/model/tools/skillcheck.hpp index 30a3f01cad..662bdadee1 100644 --- a/apps/opencs/model/tools/skillcheck.hpp +++ b/apps/opencs/model/tools/skillcheck.hpp @@ -5,12 +5,12 @@ #include "../world/idcollection.hpp" -#include "stage.hpp" +#include "../doc/stage.hpp" namespace CSMTools { /// \brief VerifyStage: make sure that skill records are internally consistent - class SkillCheckStage : public Stage + class SkillCheckStage : public CSMDoc::Stage { const CSMWorld::IdCollection& mSkills; diff --git a/apps/opencs/model/tools/soundcheck.hpp b/apps/opencs/model/tools/soundcheck.hpp index a309763a12..00b45cd935 100644 --- a/apps/opencs/model/tools/soundcheck.hpp +++ b/apps/opencs/model/tools/soundcheck.hpp @@ -5,12 +5,12 @@ #include "../world/idcollection.hpp" -#include "stage.hpp" +#include "../doc/stage.hpp" namespace CSMTools { /// \brief VerifyStage: make sure that sound records are internally consistent - class SoundCheckStage : public Stage + class SoundCheckStage : public CSMDoc::Stage { const CSMWorld::IdCollection& mSounds; diff --git a/apps/opencs/model/tools/spellcheck.hpp b/apps/opencs/model/tools/spellcheck.hpp index 0566392193..880ddafcd2 100644 --- a/apps/opencs/model/tools/spellcheck.hpp +++ b/apps/opencs/model/tools/spellcheck.hpp @@ -5,12 +5,12 @@ #include "../world/idcollection.hpp" -#include "stage.hpp" +#include "../doc/stage.hpp" namespace CSMTools { /// \brief VerifyStage: make sure that spell records are internally consistent - class SpellCheckStage : public Stage + class SpellCheckStage : public CSMDoc::Stage { const CSMWorld::IdCollection& mSpells; diff --git a/apps/opencs/model/tools/stage.cpp b/apps/opencs/model/tools/stage.cpp deleted file mode 100644 index 6f4567e579..0000000000 --- a/apps/opencs/model/tools/stage.cpp +++ /dev/null @@ -1,4 +0,0 @@ - -#include "stage.hpp" - -CSMTools::Stage::~Stage() {} \ No newline at end of file diff --git a/apps/opencs/model/tools/tools.cpp b/apps/opencs/model/tools/tools.cpp index 803861203c..1d49028ed3 100644 --- a/apps/opencs/model/tools/tools.cpp +++ b/apps/opencs/model/tools/tools.cpp @@ -21,7 +21,7 @@ #include "birthsigncheck.hpp" #include "spellcheck.hpp" -CSMTools::Operation *CSMTools::Tools::get (int type) +CSMDoc::Operation *CSMTools::Tools::get (int type) { switch (type) { @@ -31,7 +31,7 @@ CSMTools::Operation *CSMTools::Tools::get (int type) return 0; } -const CSMTools::Operation *CSMTools::Tools::get (int type) const +const CSMDoc::Operation *CSMTools::Tools::get (int type) const { return const_cast (this)->get (type); } @@ -103,7 +103,7 @@ CSMWorld::UniversalId CSMTools::Tools::runVerifier() void CSMTools::Tools::abortOperation (int type) { - if (Operation *operation = get (type)) + if (CSMDoc::Operation *operation = get (type)) operation->abort(); } @@ -118,7 +118,7 @@ int CSMTools::Tools::getRunningOperations() const int result = 0; for (int i=0; sOperations[i]!=-1; ++i) - if (const Operation *operation = get (sOperations[i])) + if (const CSMDoc::Operation *operation = get (sOperations[i])) if (operation->isRunning()) result |= sOperations[i]; diff --git a/apps/opencs/model/tools/tools.hpp b/apps/opencs/model/tools/tools.hpp index 652345c6da..693bdaa595 100644 --- a/apps/opencs/model/tools/tools.hpp +++ b/apps/opencs/model/tools/tools.hpp @@ -11,10 +11,14 @@ namespace CSMWorld class UniversalId; } +namespace CSMDoc +{ + class Operation; +} + namespace CSMTools { class Verifier; - class Operation; class ReportModel; class Tools : public QObject @@ -33,10 +37,10 @@ namespace CSMTools Verifier *getVerifier(); - Operation *get (int type); + CSMDoc::Operation *get (int type); ///< Returns a 0-pointer, if operation hasn't been used yet. - const Operation *get (int type) const; + const CSMDoc::Operation *get (int type) const; ///< Returns a 0-pointer, if operation hasn't been used yet. public: diff --git a/apps/opencs/model/tools/verifier.hpp b/apps/opencs/model/tools/verifier.hpp index 054f87169c..59edbc41b0 100644 --- a/apps/opencs/model/tools/verifier.hpp +++ b/apps/opencs/model/tools/verifier.hpp @@ -1,11 +1,11 @@ #ifndef CSM_TOOLS_VERIFIER_H #define CSM_TOOLS_VERIFIER_H -#include "operation.hpp" +#include "../doc/operation.hpp" namespace CSMTools { - class Verifier : public Operation + class Verifier : public CSMDoc::Operation { public: From f4c03c6a299cc5938aaa4c85e074aacb7c261c2b Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 14 Sep 2013 15:12:24 +0200 Subject: [PATCH 027/434] added ordered-flag to Operation (currently ignored) --- apps/opencs/model/doc/operation.cpp | 2 +- apps/opencs/model/doc/operation.hpp | 4 +++- apps/opencs/model/tools/verifier.cpp | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/opencs/model/doc/operation.cpp b/apps/opencs/model/doc/operation.cpp index 8f7472c5f6..83a374eb28 100644 --- a/apps/opencs/model/doc/operation.cpp +++ b/apps/opencs/model/doc/operation.cpp @@ -23,7 +23,7 @@ void CSMDoc::Operation::prepareStages() } } -CSMDoc::Operation::Operation (int type) : mType (type) {} +CSMDoc::Operation::Operation (int type, bool ordered) : mType (type), mOrdered (ordered) {} CSMDoc::Operation::~Operation() { diff --git a/apps/opencs/model/doc/operation.hpp b/apps/opencs/model/doc/operation.hpp index 703098852f..c7d9a038e7 100644 --- a/apps/opencs/model/doc/operation.hpp +++ b/apps/opencs/model/doc/operation.hpp @@ -19,12 +19,14 @@ namespace CSMDoc int mCurrentStep; int mCurrentStepTotal; int mTotalSteps; + int mOrdered; void prepareStages(); public: - Operation (int type); + Operation (int type, bool ordered); + ///< \param parallel Stages must be executed in the given order. virtual ~Operation(); diff --git a/apps/opencs/model/tools/verifier.cpp b/apps/opencs/model/tools/verifier.cpp index 9c00d4ea7e..d5f2071c4d 100644 --- a/apps/opencs/model/tools/verifier.cpp +++ b/apps/opencs/model/tools/verifier.cpp @@ -3,5 +3,5 @@ #include "../doc/state.hpp" -CSMTools::Verifier::Verifier() : Operation (CSMDoc::State_Verifying) +CSMTools::Verifier::Verifier() : Operation (CSMDoc::State_Verifying, false) {} From b7bffc8a7917d1b8fe1cf6009b4b6fc8726a8c6c Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 14 Sep 2013 15:16:31 +0200 Subject: [PATCH 028/434] removed Verifier class (using Operation class without subclassing now) --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/model/tools/tools.cpp | 7 +++---- apps/opencs/model/tools/tools.hpp | 5 ++--- apps/opencs/model/tools/verifier.cpp | 7 ------- apps/opencs/model/tools/verifier.hpp | 17 ----------------- 5 files changed, 6 insertions(+), 32 deletions(-) delete mode 100644 apps/opencs/model/tools/verifier.cpp delete mode 100644 apps/opencs/model/tools/verifier.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 367c43eb73..8d09eb6454 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -37,7 +37,7 @@ opencs_units (model/tools ) opencs_units_noqt (model/tools - verifier mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck + mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck birthsigncheck spellcheck ) diff --git a/apps/opencs/model/tools/tools.cpp b/apps/opencs/model/tools/tools.cpp index 1d49028ed3..1e8f4def5a 100644 --- a/apps/opencs/model/tools/tools.cpp +++ b/apps/opencs/model/tools/tools.cpp @@ -3,9 +3,8 @@ #include -#include "verifier.hpp" - #include "../doc/state.hpp" +#include "../doc/operation.hpp" #include "../world/data.hpp" #include "../world/universalid.hpp" @@ -36,11 +35,11 @@ const CSMDoc::Operation *CSMTools::Tools::get (int type) const return const_cast (this)->get (type); } -CSMTools::Verifier *CSMTools::Tools::getVerifier() +CSMDoc::Operation *CSMTools::Tools::getVerifier() { if (!mVerifier) { - mVerifier = new Verifier; + mVerifier = new CSMDoc::Operation (CSMDoc::State_Verifying, false); connect (mVerifier, SIGNAL (progress (int, int, int)), this, SIGNAL (progress (int, int, int))); connect (mVerifier, SIGNAL (finished()), this, SLOT (verifierDone())); diff --git a/apps/opencs/model/tools/tools.hpp b/apps/opencs/model/tools/tools.hpp index 693bdaa595..79c9097242 100644 --- a/apps/opencs/model/tools/tools.hpp +++ b/apps/opencs/model/tools/tools.hpp @@ -18,7 +18,6 @@ namespace CSMDoc namespace CSMTools { - class Verifier; class ReportModel; class Tools : public QObject @@ -26,7 +25,7 @@ namespace CSMTools Q_OBJECT CSMWorld::Data& mData; - Verifier *mVerifier; + CSMDoc::Operation *mVerifier; std::map mReports; int mNextReportNumber; std::map mActiveReports; // type, report number @@ -35,7 +34,7 @@ namespace CSMTools Tools (const Tools&); Tools& operator= (const Tools&); - Verifier *getVerifier(); + CSMDoc::Operation *getVerifier(); CSMDoc::Operation *get (int type); ///< Returns a 0-pointer, if operation hasn't been used yet. diff --git a/apps/opencs/model/tools/verifier.cpp b/apps/opencs/model/tools/verifier.cpp deleted file mode 100644 index d5f2071c4d..0000000000 --- a/apps/opencs/model/tools/verifier.cpp +++ /dev/null @@ -1,7 +0,0 @@ - -#include "verifier.hpp" - -#include "../doc/state.hpp" - -CSMTools::Verifier::Verifier() : Operation (CSMDoc::State_Verifying, false) -{} diff --git a/apps/opencs/model/tools/verifier.hpp b/apps/opencs/model/tools/verifier.hpp deleted file mode 100644 index 59edbc41b0..0000000000 --- a/apps/opencs/model/tools/verifier.hpp +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef CSM_TOOLS_VERIFIER_H -#define CSM_TOOLS_VERIFIER_H - -#include "../doc/operation.hpp" - -namespace CSMTools -{ - class Verifier : public CSMDoc::Operation - { - public: - - Verifier(); - - }; -} - -#endif From a5aebfb76026d6e139e0ec636430031679481ea1 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 15 Sep 2013 09:32:20 +0200 Subject: [PATCH 029/434] minor cleanup --- apps/opencs/model/doc/operation.cpp | 4 ++-- apps/opencs/model/doc/operation.hpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/opencs/model/doc/operation.cpp b/apps/opencs/model/doc/operation.cpp index 83a374eb28..d5c310b8dd 100644 --- a/apps/opencs/model/doc/operation.cpp +++ b/apps/opencs/model/doc/operation.cpp @@ -37,7 +37,7 @@ void CSMDoc::Operation::run() QTimer timer; - timer.connect (&timer, SIGNAL (timeout()), this, SLOT (verify())); + timer.connect (&timer, SIGNAL (timeout()), this, SLOT (executeStage())); timer.start (0); @@ -54,7 +54,7 @@ void CSMDoc::Operation::abort() exit(); } -void CSMDoc::Operation::verify() +void CSMDoc::Operation::executeStage() { std::vector messages; diff --git a/apps/opencs/model/doc/operation.hpp b/apps/opencs/model/doc/operation.hpp index c7d9a038e7..7b8114ecc5 100644 --- a/apps/opencs/model/doc/operation.hpp +++ b/apps/opencs/model/doc/operation.hpp @@ -49,7 +49,7 @@ namespace CSMDoc private slots: - void verify(); + void executeStage(); }; } From 414e6abb9575258dc0654082aee694cff8e86f3a Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 15 Sep 2013 11:35:12 +0200 Subject: [PATCH 030/434] more signal cleanup --- apps/opencs/model/doc/operation.cpp | 10 +++++++++- apps/opencs/model/doc/operation.hpp | 4 ++++ apps/opencs/model/tools/tools.cpp | 7 +------ apps/opencs/model/tools/tools.hpp | 2 -- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/apps/opencs/model/doc/operation.cpp b/apps/opencs/model/doc/operation.cpp index d5c310b8dd..7f47e8c706 100644 --- a/apps/opencs/model/doc/operation.cpp +++ b/apps/opencs/model/doc/operation.cpp @@ -23,7 +23,10 @@ void CSMDoc::Operation::prepareStages() } } -CSMDoc::Operation::Operation (int type, bool ordered) : mType (type), mOrdered (ordered) {} +CSMDoc::Operation::Operation (int type, bool ordered) : mType (type), mOrdered (ordered) +{ + connect (this, SIGNAL (finished()), this, SLOT (operationDone())); +} CSMDoc::Operation::~Operation() { @@ -80,4 +83,9 @@ void CSMDoc::Operation::executeStage() if (mCurrentStage==mStages.end()) exit(); +} + +void CSMDoc::Operation::operationDone() +{ + emit done (mType); } \ No newline at end of file diff --git a/apps/opencs/model/doc/operation.hpp b/apps/opencs/model/doc/operation.hpp index 7b8114ecc5..2fadbda556 100644 --- a/apps/opencs/model/doc/operation.hpp +++ b/apps/opencs/model/doc/operation.hpp @@ -43,6 +43,8 @@ namespace CSMDoc void reportMessage (const QString& message, int type); + void done (int type); + public slots: void abort(); @@ -50,6 +52,8 @@ namespace CSMDoc private slots: void executeStage(); + + void operationDone(); }; } diff --git a/apps/opencs/model/tools/tools.cpp b/apps/opencs/model/tools/tools.cpp index 1e8f4def5a..cd4653280e 100644 --- a/apps/opencs/model/tools/tools.cpp +++ b/apps/opencs/model/tools/tools.cpp @@ -42,7 +42,7 @@ CSMDoc::Operation *CSMTools::Tools::getVerifier() mVerifier = new CSMDoc::Operation (CSMDoc::State_Verifying, false); connect (mVerifier, SIGNAL (progress (int, int, int)), this, SIGNAL (progress (int, int, int))); - connect (mVerifier, SIGNAL (finished()), this, SLOT (verifierDone())); + connect (mVerifier, SIGNAL (done (int)), this, SIGNAL (done (int))); connect (mVerifier, SIGNAL (reportMessage (const QString&, int)), this, SLOT (verifierMessage (const QString&, int))); @@ -132,11 +132,6 @@ CSMTools::ReportModel *CSMTools::Tools::getReport (const CSMWorld::UniversalId& return mReports.at (id.getIndex()); } -void CSMTools::Tools::verifierDone() -{ - emit done (CSMDoc::State_Verifying); -} - void CSMTools::Tools::verifierMessage (const QString& message, int type) { std::map::iterator iter = mActiveReports.find (type); diff --git a/apps/opencs/model/tools/tools.hpp b/apps/opencs/model/tools/tools.hpp index 79c9097242..0079fab34e 100644 --- a/apps/opencs/model/tools/tools.hpp +++ b/apps/opencs/model/tools/tools.hpp @@ -61,8 +61,6 @@ namespace CSMTools private slots: - void verifierDone(); - void verifierMessage (const QString& message, int type); signals: From d71d2829527dc61ba19084b0e3c891c69ec81e86 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 15 Sep 2013 12:03:36 +0200 Subject: [PATCH 031/434] more Operation enhancements in preparation for save operation --- apps/opencs/model/doc/operation.cpp | 35 ++++++++++++++++++++++++++--- apps/opencs/model/doc/operation.hpp | 9 ++++++-- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/apps/opencs/model/doc/operation.cpp b/apps/opencs/model/doc/operation.cpp index 7f47e8c706..8af5a2c0dc 100644 --- a/apps/opencs/model/doc/operation.cpp +++ b/apps/opencs/model/doc/operation.cpp @@ -15,6 +15,7 @@ void CSMDoc::Operation::prepareStages() mCurrentStep = 0; mCurrentStepTotal = 0; mTotalSteps = 0; + mError = false; for (std::vector >::iterator iter (mStages.begin()); iter!=mStages.end(); ++iter) { @@ -23,7 +24,8 @@ void CSMDoc::Operation::prepareStages() } } -CSMDoc::Operation::Operation (int type, bool ordered) : mType (type), mOrdered (ordered) +CSMDoc::Operation::Operation (int type, bool ordered, bool finalAlways) +: mType (type), mOrdered (ordered), mFinalAlways (finalAlways) { connect (this, SIGNAL (finished()), this, SLOT (operationDone())); } @@ -52,9 +54,28 @@ void CSMDoc::Operation::appendStage (Stage *stage) mStages.push_back (std::make_pair (stage, 0)); } +bool CSMDoc::Operation::hasError() const +{ + return mError; +} + void CSMDoc::Operation::abort() { - exit(); + if (!isRunning()) + return; + + mError = true; + + if (mFinalAlways) + { + if (mStages.begin()!=mStages.end() && mCurrentStage!=--mStages.end()) + { + mCurrentStep = 0; + mCurrentStage = --mStages.end(); + } + } + else + mCurrentStage = mStages.end(); } void CSMDoc::Operation::executeStage() @@ -70,7 +91,15 @@ void CSMDoc::Operation::executeStage() } else { - mCurrentStage->first->perform (mCurrentStep++, messages); + try + { + mCurrentStage->first->perform (mCurrentStep++, messages); + } + catch (const std::exception&) + { + abort(); + } + ++mCurrentStepTotal; break; } diff --git a/apps/opencs/model/doc/operation.hpp b/apps/opencs/model/doc/operation.hpp index 2fadbda556..316eda78fd 100644 --- a/apps/opencs/model/doc/operation.hpp +++ b/apps/opencs/model/doc/operation.hpp @@ -20,13 +20,16 @@ namespace CSMDoc int mCurrentStepTotal; int mTotalSteps; int mOrdered; + bool mFinalAlways; + bool mError; void prepareStages(); public: - Operation (int type, bool ordered); - ///< \param parallel Stages must be executed in the given order. + Operation (int type, bool ordered, bool finalAlways = false); + ///< \param ordered Stages must be executed in the given order. + /// \param finalAlways Execute last stage even if an error occurred during earlier stages. virtual ~Operation(); @@ -37,6 +40,8 @@ namespace CSMDoc /// /// \attention Do no call this function while this Operation is running. + bool hasError() const; + signals: void progress (int current, int max, int type); From 8326ac9b6f271d18d7f5ff0af0b49435e2bba5f4 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 15 Sep 2013 12:48:57 +0200 Subject: [PATCH 032/434] replaced dummy save implementation with a threaded dummy save implementation --- apps/opencs/CMakeLists.txt | 4 +-- apps/opencs/model/doc/document.cpp | 47 +++++++------------------- apps/opencs/model/doc/document.hpp | 8 ++--- apps/opencs/model/doc/saving.cpp | 14 ++++++++ apps/opencs/model/doc/saving.hpp | 25 ++++++++++++++ apps/opencs/model/doc/savingstages.cpp | 30 ++++++++++++++++ apps/opencs/model/doc/savingstages.hpp | 28 +++++++++++++++ apps/opencs/model/doc/savingstate.cpp | 13 +++++++ apps/opencs/model/doc/savingstate.hpp | 22 ++++++++++++ 9 files changed, 149 insertions(+), 42 deletions(-) create mode 100644 apps/opencs/model/doc/saving.cpp create mode 100644 apps/opencs/model/doc/saving.hpp create mode 100644 apps/opencs/model/doc/savingstages.cpp create mode 100644 apps/opencs/model/doc/savingstages.hpp create mode 100644 apps/opencs/model/doc/savingstate.cpp create mode 100644 apps/opencs/model/doc/savingstate.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 8d09eb6454..b6de9295de 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -5,11 +5,11 @@ opencs_units (. editor) set (CMAKE_BUILD_TYPE DEBUG) opencs_units (model/doc - document operation + document operation saving ) opencs_units_noqt (model/doc - documentmanager stage + documentmanager stage savingstate savingstages ) opencs_hdrs_noqt (model/doc diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index d7138f6714..9b22175591 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -2141,7 +2141,7 @@ void CSMDoc::Document::createBase() CSMDoc::Document::Document (const std::vector& files, const boost::filesystem::path& savePath, bool new_) -: mSavePath (savePath), mTools (mData) +: mSavePath (savePath), mTools (mData), mSaving (*this) { if (files.empty()) throw std::runtime_error ("Empty content file sequence"); @@ -2166,9 +2166,8 @@ CSMDoc::Document::Document (const std::vector& files, connect (&mTools, SIGNAL (progress (int, int, int)), this, SLOT (progress (int, int, int))); connect (&mTools, SIGNAL (done (int)), this, SLOT (operationDone (int))); - // dummy implementation -> remove when proper save is implemented. - mSaveCount = 0; - connect (&mSaveTimer, SIGNAL(timeout()), this, SLOT (saving())); + connect (&mSaving, SIGNAL (progress (int, int, int)), this, SLOT (progress (int, int, int))); + connect (&mSaving, SIGNAL (done (int)), this, SLOT (operationDone (int))); } CSMDoc::Document::~Document() @@ -2187,7 +2186,7 @@ int CSMDoc::Document::getState() const if (!mUndoStack.isClean()) state |= State_Modified; - if (mSaveCount) + if (mSaving.isRunning()) state |= State_Locked | State_Saving | State_Operation; if (int operations = mTools.getRunningOperations()) @@ -2203,10 +2202,13 @@ const boost::filesystem::path& CSMDoc::Document::getSavePath() const void CSMDoc::Document::save() { - mSaveCount = 1; - mSaveTimer.start (500); + if (mSaving.isRunning()) + throw std::logic_error ( + "Failed to initiate save, because a save operation is already running."); + + mSaving.start(); + emit stateChanged (getState(), this); - emit progress (1, 16, State_Saving, 1, this); } CSMWorld::UniversalId CSMDoc::Document::verify() @@ -2218,17 +2220,12 @@ CSMWorld::UniversalId CSMDoc::Document::verify() void CSMDoc::Document::abortOperation (int type) { - mTools.abortOperation (type); - if (type==State_Saving) - { - mSaveCount=0; - mSaveTimer.stop(); - emit stateChanged (getState(), this); - } + mSaving.abort(); + else + mTools.abortOperation (type); } - void CSMDoc::Document::modificationStateChanged (bool clean) { emit stateChanged (getState(), this); @@ -2240,24 +2237,6 @@ void CSMDoc::Document::operationDone (int type) emit stateChanged (getState(), this); } -void CSMDoc::Document::saving() -{ - ++mSaveCount; - - emit progress (mSaveCount, 16, State_Saving, 1, this); - - if (mSaveCount>15) - { - //clear the stack before resetting the save state - //to avoid emitting incorrect states - mUndoStack.setClean(); - - mSaveCount = 0; - mSaveTimer.stop(); - emit stateChanged (getState(), this); - } -} - const CSMWorld::Data& CSMDoc::Document::getData() const { return mData; diff --git a/apps/opencs/model/doc/document.hpp b/apps/opencs/model/doc/document.hpp index 3532721ead..5a03955105 100644 --- a/apps/opencs/model/doc/document.hpp +++ b/apps/opencs/model/doc/document.hpp @@ -14,6 +14,7 @@ #include "../tools/tools.hpp" #include "state.hpp" +#include "saving.hpp" class QAbstractItemModel; @@ -34,14 +35,12 @@ namespace CSMDoc boost::filesystem::path mSavePath; CSMWorld::Data mData; CSMTools::Tools mTools; + Saving mSaving; // It is important that the undo stack is declared last, because on desctruction it fires a signal, that is connected to a slot, that is // using other member variables. Unfortunately this connection is cut only in the QObject destructor, which is way too late. QUndoStack mUndoStack; - int mSaveCount; ///< dummy implementation -> remove when proper save is implemented. - QTimer mSaveTimer; ///< dummy implementation -> remove when proper save is implemented. - // not implemented Document (const Document&); Document& operator= (const Document&); @@ -100,9 +99,6 @@ namespace CSMDoc void operationDone (int type); - void saving(); - ///< dummy implementation -> remove when proper save is implemented. - public slots: void progress (int current, int max, int type); diff --git a/apps/opencs/model/doc/saving.cpp b/apps/opencs/model/doc/saving.cpp new file mode 100644 index 0000000000..dda4ad12a8 --- /dev/null +++ b/apps/opencs/model/doc/saving.cpp @@ -0,0 +1,14 @@ + +#include "saving.hpp" + +#include "state.hpp" + +#include "savingstages.hpp" + +CSMDoc::Saving::Saving (Document& document) +: Operation (State_Saving, true, true), mDocument (document), mState (*this) +{ + + + appendStage (new FinalSavingStage (mDocument, mState)); +} \ No newline at end of file diff --git a/apps/opencs/model/doc/saving.hpp b/apps/opencs/model/doc/saving.hpp new file mode 100644 index 0000000000..b89ba5f6db --- /dev/null +++ b/apps/opencs/model/doc/saving.hpp @@ -0,0 +1,25 @@ +#ifndef CSM_DOC_SAVING_H +#define CSM_DOC_SAVING_H + +#include "operation.hpp" +#include "savingstate.hpp" + +namespace CSMDoc +{ + class Document; + + class Saving : public Operation + { + Q_OBJECT + + Document& mDocument; + SavingState mState; + + public: + + Saving (Document& document); + + }; +} + +#endif diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp new file mode 100644 index 0000000000..97facf6124 --- /dev/null +++ b/apps/opencs/model/doc/savingstages.cpp @@ -0,0 +1,30 @@ + +#include "savingstages.hpp" + +#include + +#include "document.hpp" +#include "savingstate.hpp" + +CSMDoc::FinalSavingStage::FinalSavingStage (Document& document, SavingState& state) +: mDocument (document), mState (state) +{} + +int CSMDoc::FinalSavingStage::setup() +{ + return 1; +} + +void CSMDoc::FinalSavingStage::perform (int stage, std::vector& messages) +{ + if (mState.hasError()) + { + /// \todo close stream + /// \todo delete tmp file + } + else + { + /// \todo delete file, rename tmp file + mDocument.getUndoStack().setClean(); + } +} \ No newline at end of file diff --git a/apps/opencs/model/doc/savingstages.hpp b/apps/opencs/model/doc/savingstages.hpp new file mode 100644 index 0000000000..1549c9640e --- /dev/null +++ b/apps/opencs/model/doc/savingstages.hpp @@ -0,0 +1,28 @@ +#ifndef CSM_DOC_SAVINGSTAGES_H +#define CSM_DOC_SAVINGSTAGES_H + +#include "stage.hpp" + +namespace CSMDoc +{ + class Document; + class SavingState; + + class FinalSavingStage : public Stage + { + Document& mDocument; + SavingState& mState; + + public: + + FinalSavingStage (Document& document, SavingState& state); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, std::vector& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; +} + +#endif diff --git a/apps/opencs/model/doc/savingstate.cpp b/apps/opencs/model/doc/savingstate.cpp new file mode 100644 index 0000000000..3798708593 --- /dev/null +++ b/apps/opencs/model/doc/savingstate.cpp @@ -0,0 +1,13 @@ + +#include "savingstate.hpp" + +#include "operation.hpp" + +CSMDoc::SavingState::SavingState (Operation& operation) +: mOperation (operation) +{} + +bool CSMDoc::SavingState::hasError() const +{ + return mOperation.hasError(); +} \ No newline at end of file diff --git a/apps/opencs/model/doc/savingstate.hpp b/apps/opencs/model/doc/savingstate.hpp new file mode 100644 index 0000000000..b8b6f38782 --- /dev/null +++ b/apps/opencs/model/doc/savingstate.hpp @@ -0,0 +1,22 @@ +#ifndef CSM_DOC_SAVINGSTATE_H +#define CSM_DOC_SAVINGSTATE_H + +namespace CSMDoc +{ + class Operation; + + class SavingState + { + Operation& mOperation; + + public: + + SavingState (Operation& operation); + + bool hasError() const; + }; + + +} + +#endif \ No newline at end of file From bcd36bd37843404257f1cdf65c66efbb248ef309 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 15 Sep 2013 14:55:40 +0200 Subject: [PATCH 033/434] various ESMWriter fixes --- components/esm/esmwriter.cpp | 335 +++++++++++++++++------------------ components/esm/esmwriter.hpp | 181 ++++++++++--------- components/esm/loadtes3.hpp | 2 +- 3 files changed, 262 insertions(+), 256 deletions(-) diff --git a/components/esm/esmwriter.cpp b/components/esm/esmwriter.cpp index 3ea6bd350a..a6aa82665a 100644 --- a/components/esm/esmwriter.cpp +++ b/components/esm/esmwriter.cpp @@ -2,185 +2,182 @@ #include #include -#include - -bool count = true; +#include namespace ESM { + ESMWriter::ESMWriter() : mRecordCount (0), mCounting (false) {} -int ESMWriter::getVersion() -{ - return mHeader.mData.version; -} - -void ESMWriter::setVersion(int ver) -{ - mHeader.mData.version = ver; -} - -void ESMWriter::setAuthor(const std::string& auth) -{ - mHeader.mData.author.assign (auth); -} - -void ESMWriter::setDescription(const std::string& desc) -{ - mHeader.mData.desc.assign (desc); -} - -void ESMWriter::setRecordCount (int count) -{ - mHeader.mData.records = count; -} - -void ESMWriter::setFormat (int format) -{ - mHeader.mFormat = format; -} - -void ESMWriter::addMaster(const std::string& name, uint64_t size) -{ - Header::MasterData d; - d.name = name; - d.size = size; - mHeader.mMaster.push_back(d); -} - -void ESMWriter::save(const std::string& file) -{ - std::ofstream fs(file.c_str(), std::ios_base::out | std::ios_base::trunc); - save(fs); -} - -void ESMWriter::save(std::ostream& file) -{ - m_recordCount = 0; - m_stream = &file; - - startRecord("TES3", 0); - - mHeader.save (*this); - - endRecord("TES3"); -} - -void ESMWriter::close() -{ - m_stream->flush(); - - if (!m_records.empty()) - throw "Unclosed record remaining"; -} - -void ESMWriter::startRecord(const std::string& name, uint32_t flags) -{ - m_recordCount++; - - writeName(name); - RecordData rec; - rec.name = name; - rec.position = m_stream->tellp(); - rec.size = 0; - writeT(0); // Size goes here - writeT(0); // Unused header? - writeT(flags); - m_records.push_back(rec); - - assert(m_records.back().size == 0); -} - -void ESMWriter::startSubRecord(const std::string& name) -{ - writeName(name); - RecordData rec; - rec.name = name; - rec.position = m_stream->tellp(); - rec.size = 0; - writeT(0); // Size goes here - m_records.push_back(rec); - - assert(m_records.back().size == 0); -} - -void ESMWriter::endRecord(const std::string& name) -{ - RecordData rec = m_records.back(); - assert(rec.name == name); - m_records.pop_back(); - - m_stream->seekp(rec.position); - - count = false; - write((char*)&rec.size, sizeof(int)); - count = true; - - m_stream->seekp(0, std::ios::end); - -} - -void ESMWriter::writeHNString(const std::string& name, const std::string& data) -{ - startSubRecord(name); - writeHString(data); - endRecord(name); -} - -void ESMWriter::writeHNString(const std::string& name, const std::string& data, size_t size) -{ - assert(data.size() <= size); - startSubRecord(name); - writeHString(data); - - if (data.size() < size) + unsigned int ESMWriter::getVersion() const { - for (size_t i = data.size(); i < size; ++i) - write("\0",1); + return mHeader.mData.version; } - endRecord(name); -} - -void ESMWriter::writeHString(const std::string& data) -{ - if (data.size() == 0) - write("\0", 1); - else + void ESMWriter::setVersion(unsigned int ver) { - // Convert to UTF8 and return - std::string ascii = m_encoder->getLegacyEnc(data); - - write(ascii.c_str(), ascii.size()); - } -} - -void ESMWriter::writeHCString(const std::string& data) -{ - writeHString(data); - if (data.size() > 0 && data[data.size()-1] != '\0') - write("\0", 1); -} - -void ESMWriter::writeName(const std::string& name) -{ - assert((name.size() == 4 && name[3] != '\0')); - write(name.c_str(), name.size()); -} - -void ESMWriter::write(const char* data, size_t size) -{ - if (count && !m_records.empty()) - { - for (std::list::iterator it = m_records.begin(); it != m_records.end(); ++it) - it->size += size; + mHeader.mData.version = ver; } - m_stream->write(data, size); -} + void ESMWriter::setAuthor(const std::string& auth) + { + mHeader.mData.author.assign (auth); + } -void ESMWriter::setEncoder(ToUTF8::Utf8Encoder* encoder) -{ - m_encoder = encoder; -} + void ESMWriter::setDescription(const std::string& desc) + { + mHeader.mData.desc.assign (desc); + } + void ESMWriter::setRecordCount (int count) + { + mHeader.mData.records = count; + } + + void ESMWriter::setFormat (int format) + { + mHeader.mFormat = format; + } + + void ESMWriter::addMaster(const std::string& name, uint64_t size) + { + Header::MasterData d; + d.name = name; + d.size = size; + mHeader.mMaster.push_back(d); + } + + void ESMWriter::save(const std::string& file) + { + std::ofstream fs(file.c_str(), std::ios_base::out | std::ios_base::trunc); + save(fs); + } + + void ESMWriter::save(std::ostream& file) + { + mRecordCount = 0; + mRecords.clear(); + mStream = &file; + + startRecord("TES3", 0); + + mHeader.save (*this); + + endRecord("TES3"); + } + + void ESMWriter::close() + { + if (!mRecords.empty()) + throw std::runtime_error ("Unclosed record remaining"); + } + + void ESMWriter::startRecord(const std::string& name, uint32_t flags) + { + mRecordCount++; + + writeName(name); + RecordData rec; + rec.name = name; + rec.position = mStream->tellp(); + rec.size = 0; + writeT(0); // Size goes here + writeT(0); // Unused header? + writeT(flags); + mRecords.push_back(rec); + + assert(mRecords.back().size == 0); + } + + void ESMWriter::startSubRecord(const std::string& name) + { + writeName(name); + RecordData rec; + rec.name = name; + rec.position = mStream->tellp(); + rec.size = 0; + writeT(0); // Size goes here + mRecords.push_back(rec); + + assert(mRecords.back().size == 0); + } + + void ESMWriter::endRecord(const std::string& name) + { + RecordData rec = mRecords.back(); + assert(rec.name == name); + mRecords.pop_back(); + + mStream->seekp(rec.position); + + mCounting = false; + write (reinterpret_cast (&rec.size), sizeof(int)); + mCounting = true; + + mStream->seekp(0, std::ios::end); + + } + + void ESMWriter::writeHNString(const std::string& name, const std::string& data) + { + startSubRecord(name); + writeHString(data); + endRecord(name); + } + + void ESMWriter::writeHNString(const std::string& name, const std::string& data, size_t size) + { + assert(data.size() <= size); + startSubRecord(name); + writeHString(data); + + if (data.size() < size) + { + for (size_t i = data.size(); i < size; ++i) + write("\0",1); + } + + endRecord(name); + } + + void ESMWriter::writeHString(const std::string& data) + { + if (data.size() == 0) + write("\0", 1); + else + { + // Convert to UTF8 and return + std::string ascii = mEncoder->getLegacyEnc(data); + + write(ascii.c_str(), ascii.size()); + } + } + + void ESMWriter::writeHCString(const std::string& data) + { + writeHString(data); + if (data.size() > 0 && data[data.size()-1] != '\0') + write("\0", 1); + } + + void ESMWriter::writeName(const std::string& name) + { + assert((name.size() == 4 && name[3] != '\0')); + write(name.c_str(), name.size()); + } + + void ESMWriter::write(const char* data, size_t size) + { + if (mCounting && !mRecords.empty()) + { + for (std::list::iterator it = mRecords.begin(); it != mRecords.end(); ++it) + it->size += size; + } + + mStream->write(data, size); + } + + void ESMWriter::setEncoder(ToUTF8::Utf8Encoder* encoder) + { + mEncoder = encoder; + } } diff --git a/components/esm/esmwriter.hpp b/components/esm/esmwriter.hpp index be3ae33abe..b0925463a0 100644 --- a/components/esm/esmwriter.hpp +++ b/components/esm/esmwriter.hpp @@ -13,92 +13,101 @@ namespace ESM { class ESMWriter { - struct RecordData - { - std::string name; - std::streampos position; - size_t size; + struct RecordData + { + std::string name; + std::streampos position; + size_t size; + }; + + public: + + ESMWriter(); + + unsigned int getVersion() const; + void setVersion(unsigned int ver = 0x3fa66666); + void setEncoder(ToUTF8::Utf8Encoder *encoding); + void setAuthor(const std::string& author); + void setDescription(const std::string& desc); + void setRecordCount (int count); + void setFormat (int format); + + void addMaster(const std::string& name, uint64_t size); + + void save(const std::string& file); + ///< Start saving a file by writing the TES3 header. + + void save(std::ostream& file); + ///< Start saving a file by writing the TES3 header. + + void close(); + ///< \note Does not close the stream. + + void writeHNString(const std::string& name, const std::string& data); + void writeHNString(const std::string& name, const std::string& data, size_t size); + void writeHNCString(const std::string& name, const std::string& data) + { + startSubRecord(name); + writeHCString(data); + endRecord(name); + } + void writeHNOString(const std::string& name, const std::string& data) + { + if (!data.empty()) + writeHNString(name, data); + } + void writeHNOCString(const std::string& name, const std::string& data) + { + if (!data.empty()) + writeHNCString(name, data); + } + + template + void writeHNT(const std::string& name, const T& data) + { + startSubRecord(name); + writeT(data); + endRecord(name); + } + + template + void writeHNT(const std::string& name, const T& data, int size) + { + startSubRecord(name); + writeT(data, size); + endRecord(name); + } + + template + void writeT(const T& data) + { + write((char*)&data, sizeof(T)); + } + + template + void writeT(const T& data, size_t size) + { + write((char*)&data, size); + } + + void startRecord(const std::string& name, uint32_t flags); + void startSubRecord(const std::string& name); + void endRecord(const std::string& name); + void writeHString(const std::string& data); + void writeHCString(const std::string& data); + void writeName(const std::string& data); + void write(const char* data, size_t size); + + private: + std::list mRecords; + std::ostream* mStream; + std::streampos mHeaderPos; + ToUTF8::Utf8Encoder* mEncoder; + int mRecordCount; + bool mCounting; + + Header mHeader; }; - -public: - int getVersion(); - void setVersion(int ver); - void setEncoder(ToUTF8::Utf8Encoder *encoding); // Write strings as UTF-8? - void setAuthor(const std::string& author); - void setDescription(const std::string& desc); - void setRecordCount (int count); - void setFormat (int format); - - void addMaster(const std::string& name, uint64_t size); - - void save(const std::string& file); - void save(std::ostream& file); - void close(); - - void writeHNString(const std::string& name, const std::string& data); - void writeHNString(const std::string& name, const std::string& data, size_t size); - void writeHNCString(const std::string& name, const std::string& data) - { - startSubRecord(name); - writeHCString(data); - endRecord(name); - } - void writeHNOString(const std::string& name, const std::string& data) - { - if (!data.empty()) - writeHNString(name, data); - } - void writeHNOCString(const std::string& name, const std::string& data) - { - if (!data.empty()) - writeHNCString(name, data); - } - - template - void writeHNT(const std::string& name, const T& data) - { - startSubRecord(name); - writeT(data); - endRecord(name); - } - - template - void writeHNT(const std::string& name, const T& data, int size) - { - startSubRecord(name); - writeT(data, size); - endRecord(name); - } - - template - void writeT(const T& data) - { - write((char*)&data, sizeof(T)); - } - - template - void writeT(const T& data, size_t size) - { - write((char*)&data, size); - } - - void startRecord(const std::string& name, uint32_t flags); - void startSubRecord(const std::string& name); - void endRecord(const std::string& name); - void writeHString(const std::string& data); - void writeHCString(const std::string& data); - void writeName(const std::string& data); - void write(const char* data, size_t size); - -private: - std::list m_records; - std::ostream* m_stream; - std::streampos m_headerPos; - ToUTF8::Utf8Encoder* m_encoder; - int m_recordCount; - - Header mHeader; -}; - } + #endif diff --git a/components/esm/loadtes3.hpp b/components/esm/loadtes3.hpp index b73a4c31e4..5614d295f6 100644 --- a/components/esm/loadtes3.hpp +++ b/components/esm/loadtes3.hpp @@ -24,7 +24,7 @@ namespace ESM versions are 1.2 and 1.3. These correspond to: 1.2 = 0x3f99999a and 1.3 = 0x3fa66666 */ - int version; + unsigned int version; int type; // 0=esp, 1=esm, 32=ess (unused) NAME32 author; // Author's name NAME256 desc; // File description From fa25a068a8681202a8327cde663273d9910fd838 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 15 Sep 2013 15:00:41 +0200 Subject: [PATCH 034/434] basic saving (no data is written to file yet) --- apps/opencs/model/doc/saving.cpp | 3 ++ apps/opencs/model/doc/savingstages.cpp | 53 ++++++++++++++++++++++++-- apps/opencs/model/doc/savingstages.hpp | 31 +++++++++++++++ apps/opencs/model/doc/savingstate.cpp | 45 +++++++++++++++++++++- apps/opencs/model/doc/savingstate.hpp | 22 +++++++++++ 5 files changed, 149 insertions(+), 5 deletions(-) diff --git a/apps/opencs/model/doc/saving.cpp b/apps/opencs/model/doc/saving.cpp index dda4ad12a8..5607883adf 100644 --- a/apps/opencs/model/doc/saving.cpp +++ b/apps/opencs/model/doc/saving.cpp @@ -8,7 +8,10 @@ CSMDoc::Saving::Saving (Document& document) : Operation (State_Saving, true, true), mDocument (document), mState (*this) { + appendStage (new OpenSaveStage (mDocument, mState)); + appendStage (new CloseSaveStage (mState)); + appendStage (new FinalSavingStage (mDocument, mState)); } \ No newline at end of file diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index 97facf6124..078762992a 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -1,11 +1,53 @@ #include "savingstages.hpp" +#include + +#include + #include #include "document.hpp" #include "savingstate.hpp" +CSMDoc::OpenSaveStage::OpenSaveStage (Document& document, SavingState& state) +: mDocument (document), mState (state) +{} + +int CSMDoc::OpenSaveStage::setup() +{ + return 1; +} + +void CSMDoc::OpenSaveStage::perform (int stage, std::vector& messages) +{ + mState.start (mDocument); + + mState.getStream().open (mState.getTmpPath().string().c_str()); + + if (!mState.getStream().is_open()) + throw std::runtime_error ("failed to open stream for saving"); +} + + +CSMDoc::CloseSaveStage::CloseSaveStage (SavingState& state) +: mState (state) +{} + +int CSMDoc::CloseSaveStage::setup() +{ + return 1; +} + +void CSMDoc::CloseSaveStage::perform (int stage, std::vector& messages) +{ + mState.getStream().close(); + + if (!mState.getStream()) + throw std::runtime_error ("saving failed"); +} + + CSMDoc::FinalSavingStage::FinalSavingStage (Document& document, SavingState& state) : mDocument (document), mState (state) {} @@ -19,12 +61,17 @@ void CSMDoc::FinalSavingStage::perform (int stage, std::vector& mes { if (mState.hasError()) { - /// \todo close stream - /// \todo delete tmp file + mState.getWriter().close(); + mState.getStream().close(); + + if (boost::filesystem::exists (mState.getTmpPath())) + boost::filesystem::remove (mState.getTmpPath()); } else { - /// \todo delete file, rename tmp file + boost::filesystem::remove (mState.getPath()); + boost::filesystem::rename (mState.getTmpPath(), mState.getPath()); + mDocument.getUndoStack().setClean(); } } \ No newline at end of file diff --git a/apps/opencs/model/doc/savingstages.hpp b/apps/opencs/model/doc/savingstages.hpp index 1549c9640e..0b64896f76 100644 --- a/apps/opencs/model/doc/savingstages.hpp +++ b/apps/opencs/model/doc/savingstages.hpp @@ -8,6 +8,37 @@ namespace CSMDoc class Document; class SavingState; + class OpenSaveStage : public Stage + { + Document& mDocument; + SavingState& mState; + + public: + + OpenSaveStage (Document& document, SavingState& state); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, std::vector& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; + + class CloseSaveStage : public Stage + { + SavingState& mState; + + public: + + CloseSaveStage (SavingState& state); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, std::vector& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; + class FinalSavingStage : public Stage { Document& mDocument; diff --git a/apps/opencs/model/doc/savingstate.cpp b/apps/opencs/model/doc/savingstate.cpp index 3798708593..a49a0699b2 100644 --- a/apps/opencs/model/doc/savingstate.cpp +++ b/apps/opencs/model/doc/savingstate.cpp @@ -2,12 +2,53 @@ #include "savingstate.hpp" #include "operation.hpp" +#include "document.hpp" CSMDoc::SavingState::SavingState (Operation& operation) -: mOperation (operation) -{} +: mOperation (operation), + /// \todo set encoding properly, once config implementation has been fixed. + mEncoder (ToUTF8::calculateEncoding ("win1252")) +{ + mWriter.setEncoder (&mEncoder); +} bool CSMDoc::SavingState::hasError() const { return mOperation.hasError(); +} + +void CSMDoc::SavingState::start (Document& document) +{ + if (mStream.is_open()) + mStream.close(); + + mStream.clear(); + + mPath = document.getSavePath(); + + boost::filesystem::path file (mPath.filename().string() + ".tmp"); + + mTmpPath = mPath.parent_path(); + + mTmpPath /= file; +} + +const boost::filesystem::path& CSMDoc::SavingState::getPath() const +{ + return mPath; +} + +const boost::filesystem::path& CSMDoc::SavingState::getTmpPath() const +{ + return mTmpPath; +} + +std::ofstream& CSMDoc::SavingState::getStream() +{ + return mStream; +} + +ESM::ESMWriter& CSMDoc::SavingState::getWriter() +{ + return mWriter; } \ No newline at end of file diff --git a/apps/opencs/model/doc/savingstate.hpp b/apps/opencs/model/doc/savingstate.hpp index b8b6f38782..3f42b4653d 100644 --- a/apps/opencs/model/doc/savingstate.hpp +++ b/apps/opencs/model/doc/savingstate.hpp @@ -1,19 +1,41 @@ #ifndef CSM_DOC_SAVINGSTATE_H #define CSM_DOC_SAVINGSTATE_H +#include + +#include + +#include + namespace CSMDoc { class Operation; + class Document; class SavingState { Operation& mOperation; + boost::filesystem::path mPath; + boost::filesystem::path mTmpPath; + ToUTF8::Utf8Encoder mEncoder; + std::ofstream mStream; + ESM::ESMWriter mWriter; public: SavingState (Operation& operation); bool hasError() const; + + void start (Document& document); + + const boost::filesystem::path& getPath() const; + + const boost::filesystem::path& getTmpPath() const; + + std::ofstream& getStream(); + + ESM::ESMWriter& getWriter(); }; From 231419028d7bdd83bc61e17fbed2ef25500bf0bd Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 15 Sep 2013 15:03:17 +0200 Subject: [PATCH 035/434] minor fix --- apps/opencs/model/doc/savingstages.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index 078762992a..e7c9799ecd 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -69,7 +69,9 @@ void CSMDoc::FinalSavingStage::perform (int stage, std::vector& mes } else { - boost::filesystem::remove (mState.getPath()); + if (boost::filesystem::exists (mState.getPath())) + boost::filesystem::remove (mState.getPath()); + boost::filesystem::rename (mState.getTmpPath(), mState.getPath()); mDocument.getUndoStack().setClean(); From 1ee228a56614c0a512c2abf4fbb82ac235a1a2c5 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 15 Sep 2013 15:30:17 +0200 Subject: [PATCH 036/434] fix for the ESMWriter fix --- components/esm/esmwriter.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/esm/esmwriter.cpp b/components/esm/esmwriter.cpp index a6aa82665a..95ad44811d 100644 --- a/components/esm/esmwriter.cpp +++ b/components/esm/esmwriter.cpp @@ -6,7 +6,7 @@ namespace ESM { - ESMWriter::ESMWriter() : mRecordCount (0), mCounting (false) {} + ESMWriter::ESMWriter() : mRecordCount (0), mCounting (true) {} unsigned int ESMWriter::getVersion() const { @@ -56,6 +56,7 @@ namespace ESM { mRecordCount = 0; mRecords.clear(); + mCounting = true; mStream = &file; startRecord("TES3", 0); From db70095148a7df4c2c171fe58d51c907cc2c1492 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 15 Sep 2013 15:31:44 +0200 Subject: [PATCH 037/434] write TES3 header --- apps/opencs/model/doc/saving.cpp | 3 +++ apps/opencs/model/doc/savingstages.cpp | 26 ++++++++++++++++++++++++++ apps/opencs/model/doc/savingstages.hpp | 16 ++++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/apps/opencs/model/doc/saving.cpp b/apps/opencs/model/doc/saving.cpp index 5607883adf..67073ca434 100644 --- a/apps/opencs/model/doc/saving.cpp +++ b/apps/opencs/model/doc/saving.cpp @@ -10,6 +10,9 @@ CSMDoc::Saving::Saving (Document& document) { appendStage (new OpenSaveStage (mDocument, mState)); + appendStage (new WriteHeaderStage (mDocument, mState)); + + appendStage (new CloseSaveStage (mState)); diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index e7c9799ecd..797b32eae8 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -30,6 +30,32 @@ void CSMDoc::OpenSaveStage::perform (int stage, std::vector& messag } +CSMDoc::WriteHeaderStage::WriteHeaderStage (Document& document, SavingState& state) +: mDocument (document), mState (state) +{} + +int CSMDoc::WriteHeaderStage::setup() +{ + return 1; +} + +void CSMDoc::WriteHeaderStage::perform (int stage, std::vector& messages) +{ + mState.getWriter().setVersion(); + + mState.getWriter().setFormat (0); + + /// \todo fill in missing header information + mState.getWriter().setAuthor (""); + mState.getWriter().setDescription (""); + mState.getWriter().setRecordCount (0); + + /// \todo fill in dependency list + + mState.getWriter().save (mState.getStream()); +} + + CSMDoc::CloseSaveStage::CloseSaveStage (SavingState& state) : mState (state) {} diff --git a/apps/opencs/model/doc/savingstages.hpp b/apps/opencs/model/doc/savingstages.hpp index 0b64896f76..914a2d585a 100644 --- a/apps/opencs/model/doc/savingstages.hpp +++ b/apps/opencs/model/doc/savingstages.hpp @@ -24,6 +24,22 @@ namespace CSMDoc ///< Messages resulting from this stage will be appended to \a messages. }; + class WriteHeaderStage : public Stage + { + Document& mDocument; + SavingState& mState; + + public: + + WriteHeaderStage (Document& document, SavingState& state); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, std::vector& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; + class CloseSaveStage : public Stage { SavingState& mState; From 874ce26bef34ed6b14498b80aa1ec37a5120c377 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 16 Sep 2013 12:32:35 +0200 Subject: [PATCH 038/434] added missing consts to record save functions --- components/esm/aipackage.cpp | 4 ++-- components/esm/aipackage.hpp | 2 +- components/esm/cellref.cpp | 2 +- components/esm/cellref.hpp | 2 +- components/esm/effectlist.cpp | 4 ++-- components/esm/effectlist.hpp | 4 ++-- components/esm/filter.cpp | 2 +- components/esm/filter.hpp | 2 +- components/esm/loadacti.cpp | 2 +- components/esm/loadacti.hpp | 2 +- components/esm/loadalch.cpp | 2 +- components/esm/loadalch.hpp | 2 +- components/esm/loadappa.cpp | 2 +- components/esm/loadappa.hpp | 2 +- components/esm/loadarmo.cpp | 6 +++--- components/esm/loadarmo.hpp | 4 ++-- components/esm/loadbody.cpp | 2 +- components/esm/loadbody.hpp | 2 +- components/esm/loadbook.cpp | 2 +- components/esm/loadbook.hpp | 2 +- components/esm/loadbsgn.cpp | 2 +- components/esm/loadbsgn.hpp | 2 +- components/esm/loadcell.cpp | 2 +- components/esm/loadcell.hpp | 2 +- components/esm/loadclas.cpp | 2 +- components/esm/loadclas.hpp | 2 +- components/esm/loadclot.cpp | 2 +- components/esm/loadclot.hpp | 2 +- components/esm/loadcont.cpp | 6 +++--- components/esm/loadcont.hpp | 4 ++-- components/esm/loadcrea.cpp | 2 +- components/esm/loadcrea.hpp | 2 +- components/esm/loadcrec.hpp | 4 ++-- components/esm/loaddial.cpp | 2 +- components/esm/loaddial.hpp | 2 +- components/esm/loaddoor.cpp | 2 +- components/esm/loaddoor.hpp | 2 +- components/esm/loadench.cpp | 2 +- components/esm/loadench.hpp | 2 +- components/esm/loadfact.cpp | 4 ++-- components/esm/loadfact.hpp | 2 +- components/esm/loadglob.cpp | 2 +- components/esm/loadglob.hpp | 2 +- components/esm/loadgmst.cpp | 2 +- components/esm/loadgmst.hpp | 4 ++-- components/esm/loadinfo.cpp | 4 ++-- components/esm/loadinfo.hpp | 2 +- components/esm/loadingr.cpp | 2 +- components/esm/loadingr.hpp | 2 +- components/esm/loadland.cpp | 18 +++--------------- components/esm/loadland.hpp | 2 +- components/esm/loadlevlist.cpp | 4 ++-- components/esm/loadlevlist.hpp | 2 +- components/esm/loadligh.cpp | 2 +- components/esm/loadligh.hpp | 2 +- components/esm/loadlock.cpp | 2 +- components/esm/loadlock.hpp | 2 +- components/esm/loadltex.cpp | 2 +- components/esm/loadltex.hpp | 2 +- components/esm/loadmgef.cpp | 10 +++------- components/esm/loadmgef.hpp | 2 +- components/esm/loadmisc.cpp | 2 +- components/esm/loadmisc.hpp | 2 +- components/esm/loadnpc.cpp | 4 ++-- components/esm/loadnpc.hpp | 2 +- components/esm/loadnpcc.hpp | 2 +- components/esm/loadpgrd.cpp | 10 +++++----- components/esm/loadpgrd.hpp | 2 +- components/esm/loadprob.cpp | 2 +- components/esm/loadprob.hpp | 2 +- components/esm/loadrace.cpp | 2 +- components/esm/loadrace.hpp | 2 +- components/esm/loadregn.cpp | 4 ++-- components/esm/loadregn.hpp | 2 +- components/esm/loadrepa.cpp | 2 +- components/esm/loadrepa.hpp | 2 +- components/esm/loadscpt.cpp | 6 +++--- components/esm/loadscpt.hpp | 2 +- components/esm/loadskil.cpp | 2 +- components/esm/loadskil.hpp | 2 +- components/esm/loadsndg.cpp | 2 +- components/esm/loadsndg.hpp | 2 +- components/esm/loadsoun.cpp | 2 +- components/esm/loadsoun.hpp | 2 +- components/esm/loadspel.cpp | 2 +- components/esm/loadspel.hpp | 2 +- components/esm/loadsscr.cpp | 2 +- components/esm/loadsscr.hpp | 2 +- components/esm/loadstat.cpp | 2 +- components/esm/loadstat.hpp | 2 +- components/esm/loadweap.cpp | 2 +- components/esm/loadweap.hpp | 2 +- components/esm/spelllist.cpp | 4 ++-- components/esm/spelllist.hpp | 2 +- 94 files changed, 121 insertions(+), 137 deletions(-) diff --git a/components/esm/aipackage.cpp b/components/esm/aipackage.cpp index 1440dbd138..cf4951de7e 100644 --- a/components/esm/aipackage.cpp +++ b/components/esm/aipackage.cpp @@ -44,9 +44,9 @@ namespace ESM } } - void AIPackageList::save(ESMWriter &esm) + void AIPackageList::save(ESMWriter &esm) const { - typedef std::vector::iterator PackageIter; + typedef std::vector::const_iterator PackageIter; for (PackageIter it = mList.begin(); it != mList.end(); ++it) { switch (it->mType) { case AI_Wander: diff --git a/components/esm/aipackage.hpp b/components/esm/aipackage.hpp index 38499b2dd8..b06cb529a7 100644 --- a/components/esm/aipackage.hpp +++ b/components/esm/aipackage.hpp @@ -93,7 +93,7 @@ namespace ESM /// it needs to use retSubName() if needed. But, hey, there /// is only one field left (XSCL) and only two records uses AI void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; }; } diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp index 95cf24d331..e91059b26f 100644 --- a/components/esm/cellref.cpp +++ b/components/esm/cellref.cpp @@ -3,7 +3,7 @@ #include "esmwriter.hpp" -void ESM::CellRef::save(ESMWriter &esm) +void ESM::CellRef::save(ESMWriter &esm) const { esm.writeHNT("FRMR", mRefnum); esm.writeHNCString("NAME", mRefID); diff --git a/components/esm/cellref.hpp b/components/esm/cellref.hpp index 31889914ce..47cb0b99ed 100644 --- a/components/esm/cellref.hpp +++ b/components/esm/cellref.hpp @@ -83,7 +83,7 @@ namespace ESM // Position and rotation of this object within the cell Position mPos; - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); }; diff --git a/components/esm/effectlist.cpp b/components/esm/effectlist.cpp index 88f87d6e29..bc126846b1 100644 --- a/components/esm/effectlist.cpp +++ b/components/esm/effectlist.cpp @@ -14,9 +14,9 @@ void EffectList::load(ESMReader &esm) } } -void EffectList::save(ESMWriter &esm) +void EffectList::save(ESMWriter &esm) const { - for (std::vector::iterator it = mList.begin(); it != mList.end(); ++it) { + for (std::vector::const_iterator it = mList.begin(); it != mList.end(); ++it) { esm.writeHNT("ENAM", *it, 24); } } diff --git a/components/esm/effectlist.hpp b/components/esm/effectlist.hpp index 9f5b87aeda..04adcc5cd8 100644 --- a/components/esm/effectlist.hpp +++ b/components/esm/effectlist.hpp @@ -35,9 +35,9 @@ namespace ESM std::vector mList; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; }; - + } #endif diff --git a/components/esm/filter.cpp b/components/esm/filter.cpp index 7d4851a5f6..96cc19d43d 100644 --- a/components/esm/filter.cpp +++ b/components/esm/filter.cpp @@ -10,7 +10,7 @@ void ESM::Filter::load (ESMReader& esm) mDescription = esm.getHNString ("DESC"); } -void ESM::Filter::save (ESMWriter& esm) +void ESM::Filter::save (ESMWriter& esm) const { esm.writeHNCString ("FILT", mFilter); esm.writeHNCString ("DESC", mDescription); diff --git a/components/esm/filter.hpp b/components/esm/filter.hpp index 0fd564361e..a44d1b1980 100644 --- a/components/esm/filter.hpp +++ b/components/esm/filter.hpp @@ -17,7 +17,7 @@ namespace ESM std::string mFilter; void load (ESMReader& esm); - void save (ESMWriter& esm); + void save (ESMWriter& esm) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadacti.cpp b/components/esm/loadacti.cpp index fd022af7e6..dcae845d0a 100644 --- a/components/esm/loadacti.cpp +++ b/components/esm/loadacti.cpp @@ -11,7 +11,7 @@ void Activator::load(ESMReader &esm) mName = esm.getHNString("FNAM"); mScript = esm.getHNOString("SCRI"); } -void Activator::save(ESMWriter &esm) +void Activator::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); esm.writeHNCString("FNAM", mName); diff --git a/components/esm/loadacti.hpp b/components/esm/loadacti.hpp index a62990590d..6b072ee11e 100644 --- a/components/esm/loadacti.hpp +++ b/components/esm/loadacti.hpp @@ -14,7 +14,7 @@ struct Activator std::string mId, mName, mScript, mModel; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadalch.cpp b/components/esm/loadalch.cpp index dbb69c066f..187069c2ef 100644 --- a/components/esm/loadalch.cpp +++ b/components/esm/loadalch.cpp @@ -14,7 +14,7 @@ void Potion::load(ESMReader &esm) esm.getHNT(mData, "ALDT", 12); mEffects.load(esm); } -void Potion::save(ESMWriter &esm) +void Potion::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); esm.writeHNOCString("TEXT", mIcon); diff --git a/components/esm/loadalch.hpp b/components/esm/loadalch.hpp index 3ede853424..8f0435292e 100644 --- a/components/esm/loadalch.hpp +++ b/components/esm/loadalch.hpp @@ -29,7 +29,7 @@ struct Potion EffectList mEffects; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadappa.cpp b/components/esm/loadappa.cpp index 4b8d2b763c..01233a0558 100644 --- a/components/esm/loadappa.cpp +++ b/components/esm/loadappa.cpp @@ -28,7 +28,7 @@ void Apparatus::load(ESMReader &esm) } } -void Apparatus::save(ESMWriter &esm) +void Apparatus::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); esm.writeHNCString("FNAM", mName); diff --git a/components/esm/loadappa.hpp b/components/esm/loadappa.hpp index ed9d335be6..d47643c6ce 100644 --- a/components/esm/loadappa.hpp +++ b/components/esm/loadappa.hpp @@ -35,7 +35,7 @@ struct Apparatus std::string mId, mModel, mIcon, mScript, mName; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadarmo.cpp b/components/esm/loadarmo.cpp index e64c8705d7..4dbdf1314d 100644 --- a/components/esm/loadarmo.cpp +++ b/components/esm/loadarmo.cpp @@ -18,9 +18,9 @@ void PartReferenceList::load(ESMReader &esm) } } -void PartReferenceList::save(ESMWriter &esm) +void PartReferenceList::save(ESMWriter &esm) const { - for (std::vector::iterator it = mParts.begin(); it != mParts.end(); ++it) + for (std::vector::const_iterator it = mParts.begin(); it != mParts.end(); ++it) { esm.writeHNT("INDX", it->mPart); esm.writeHNOString("BNAM", it->mMale); @@ -39,7 +39,7 @@ void Armor::load(ESMReader &esm) mEnchant = esm.getHNOString("ENAM"); } -void Armor::save(ESMWriter &esm) +void Armor::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); esm.writeHNCString("FNAM", mName); diff --git a/components/esm/loadarmo.hpp b/components/esm/loadarmo.hpp index eaef42be83..5a38605e3d 100644 --- a/components/esm/loadarmo.hpp +++ b/components/esm/loadarmo.hpp @@ -56,7 +56,7 @@ struct PartReferenceList std::vector mParts; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; }; struct Armor @@ -89,7 +89,7 @@ struct Armor std::string mId, mName, mModel, mIcon, mScript, mEnchant; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadbody.cpp b/components/esm/loadbody.cpp index e95a8a8603..a5d986f65d 100644 --- a/components/esm/loadbody.cpp +++ b/components/esm/loadbody.cpp @@ -12,7 +12,7 @@ void BodyPart::load(ESMReader &esm) mRace = esm.getHNString("FNAM"); esm.getHNT(mData, "BYDT", 4); } -void BodyPart::save(ESMWriter &esm) +void BodyPart::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); esm.writeHNCString("FNAM", mRace); diff --git a/components/esm/loadbody.hpp b/components/esm/loadbody.hpp index 3ad9b1b958..a8fd36aef4 100644 --- a/components/esm/loadbody.hpp +++ b/components/esm/loadbody.hpp @@ -57,7 +57,7 @@ struct BodyPart std::string mId, mModel, mRace; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; }; } #endif diff --git a/components/esm/loadbook.cpp b/components/esm/loadbook.cpp index 3a70ac7869..d9db118899 100644 --- a/components/esm/loadbook.cpp +++ b/components/esm/loadbook.cpp @@ -16,7 +16,7 @@ void Book::load(ESMReader &esm) mText = esm.getHNOString("TEXT"); mEnchant = esm.getHNOString("ENAM"); } -void Book::save(ESMWriter &esm) +void Book::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); diff --git a/components/esm/loadbook.hpp b/components/esm/loadbook.hpp index 68042e246e..688e9dd75e 100644 --- a/components/esm/loadbook.hpp +++ b/components/esm/loadbook.hpp @@ -25,7 +25,7 @@ struct Book std::string mId; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadbsgn.cpp b/components/esm/loadbsgn.cpp index cb500f6748..9d19f02c7e 100644 --- a/components/esm/loadbsgn.cpp +++ b/components/esm/loadbsgn.cpp @@ -15,7 +15,7 @@ void BirthSign::load(ESMReader &esm) mPowers.load(esm); } -void BirthSign::save(ESMWriter &esm) +void BirthSign::save(ESMWriter &esm) const { esm.writeHNCString("FNAM", mName); esm.writeHNOCString("TNAM", mTexture); diff --git a/components/esm/loadbsgn.hpp b/components/esm/loadbsgn.hpp index 434ddf68ea..1ecb5e418e 100644 --- a/components/esm/loadbsgn.hpp +++ b/components/esm/loadbsgn.hpp @@ -19,7 +19,7 @@ struct BirthSign SpellList mPowers; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadcell.cpp b/components/esm/loadcell.cpp index d8d0c12912..57d3278d8d 100644 --- a/components/esm/loadcell.cpp +++ b/components/esm/loadcell.cpp @@ -89,7 +89,7 @@ void Cell::postLoad(ESMReader &esm) esm.skipRecord(); } -void Cell::save(ESMWriter &esm) +void Cell::save(ESMWriter &esm) const { esm.writeHNT("DATA", mData, 12); if (mData.mFlags & Interior) diff --git a/components/esm/loadcell.hpp b/components/esm/loadcell.hpp index 51288b2919..c417fceab8 100644 --- a/components/esm/loadcell.hpp +++ b/components/esm/loadcell.hpp @@ -102,7 +102,7 @@ struct Cell // This method is left in for compatibility with esmtool. Parsing moved references currently requires // passing ESMStore, bit it does not know about this parameter, so we do it this way. void load(ESMReader &esm, bool saveContext = true); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; bool isExterior() const { diff --git a/components/esm/loadclas.cpp b/components/esm/loadclas.cpp index bdc4614625..ef07430c7a 100644 --- a/components/esm/loadclas.cpp +++ b/components/esm/loadclas.cpp @@ -47,7 +47,7 @@ void Class::load(ESMReader &esm) mDescription = esm.getHNOString("DESC"); } -void Class::save(ESMWriter &esm) +void Class::save(ESMWriter &esm) const { esm.writeHNCString("FNAM", mName); esm.writeHNT("CLDT", mData, 60); diff --git a/components/esm/loadclas.hpp b/components/esm/loadclas.hpp index 4f85e6ee8b..f241dca8da 100644 --- a/components/esm/loadclas.hpp +++ b/components/esm/loadclas.hpp @@ -70,7 +70,7 @@ struct Class CLDTstruct mData; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadclot.cpp b/components/esm/loadclot.cpp index 10b00970fb..c623155dfa 100644 --- a/components/esm/loadclot.cpp +++ b/components/esm/loadclot.cpp @@ -20,7 +20,7 @@ void Clothing::load(ESMReader &esm) mEnchant = esm.getHNOString("ENAM"); } -void Clothing::save(ESMWriter &esm) +void Clothing::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); diff --git a/components/esm/loadclot.hpp b/components/esm/loadclot.hpp index 816d03cb23..13fae865b4 100644 --- a/components/esm/loadclot.hpp +++ b/components/esm/loadclot.hpp @@ -45,7 +45,7 @@ struct Clothing std::string mId, mName, mModel, mIcon, mEnchant, mScript; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadcont.cpp b/components/esm/loadcont.cpp index 853c8bd500..0cbb4acd1e 100644 --- a/components/esm/loadcont.cpp +++ b/components/esm/loadcont.cpp @@ -16,9 +16,9 @@ void InventoryList::load(ESMReader &esm) } } -void InventoryList::save(ESMWriter &esm) +void InventoryList::save(ESMWriter &esm) const { - for (std::vector::iterator it = mList.begin(); it != mList.end(); ++it) + for (std::vector::const_iterator it = mList.begin(); it != mList.end(); ++it) { esm.writeHNT("NPCO", *it, 36); } @@ -41,7 +41,7 @@ void Container::load(ESMReader &esm) mInventory.load(esm); } -void Container::save(ESMWriter &esm) +void Container::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); diff --git a/components/esm/loadcont.hpp b/components/esm/loadcont.hpp index b2bbab73d0..c854b52907 100644 --- a/components/esm/loadcont.hpp +++ b/components/esm/loadcont.hpp @@ -27,7 +27,7 @@ struct InventoryList std::vector mList; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; }; struct Container @@ -46,7 +46,7 @@ struct Container InventoryList mInventory; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadcrea.cpp b/components/esm/loadcrea.cpp index 86d05b8a57..30b70b35b6 100644 --- a/components/esm/loadcrea.cpp +++ b/components/esm/loadcrea.cpp @@ -35,7 +35,7 @@ void Creature::load(ESMReader &esm) esm.skipRecord(); } -void Creature::save(ESMWriter &esm) +void Creature::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); esm.writeHNOCString("CNAM", mOriginal); diff --git a/components/esm/loadcrea.hpp b/components/esm/loadcrea.hpp index 279e2ea3f4..80e0fbd1c9 100644 --- a/components/esm/loadcrea.hpp +++ b/components/esm/loadcrea.hpp @@ -86,7 +86,7 @@ struct Creature AIPackageList mAiPackage; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadcrec.hpp b/components/esm/loadcrec.hpp index 6904df15a4..2b840ccf46 100644 --- a/components/esm/loadcrec.hpp +++ b/components/esm/loadcrec.hpp @@ -24,7 +24,7 @@ struct LoadCREC esm.skipRecord(); } - void save(ESMWriter &esm) + void save(ESMWriter &esm) const { } }; @@ -39,7 +39,7 @@ struct LoadCNTC esm.skipRecord(); } - void save(ESMWriter &esm) + void save(ESMWriter &esm) const { } }; diff --git a/components/esm/loaddial.cpp b/components/esm/loaddial.cpp index fb50d5e9f5..e014ca37e8 100644 --- a/components/esm/loaddial.cpp +++ b/components/esm/loaddial.cpp @@ -25,7 +25,7 @@ void Dialogue::load(ESMReader &esm) esm.fail("Unknown sub record size"); } -void Dialogue::save(ESMWriter &esm) +void Dialogue::save(ESMWriter &esm) const { if (mType != Deleted) esm.writeHNT("DATA", mType); diff --git a/components/esm/loaddial.hpp b/components/esm/loaddial.hpp index 61f3f763de..0fe5027dc9 100644 --- a/components/esm/loaddial.hpp +++ b/components/esm/loaddial.hpp @@ -34,7 +34,7 @@ struct Dialogue std::vector mInfo; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; }; } #endif diff --git a/components/esm/loaddoor.cpp b/components/esm/loaddoor.cpp index a4c7b7d58b..f666ac67a9 100644 --- a/components/esm/loaddoor.cpp +++ b/components/esm/loaddoor.cpp @@ -15,7 +15,7 @@ void Door::load(ESMReader &esm) mCloseSound = esm.getHNOString("ANAM"); } -void Door::save(ESMWriter &esm) +void Door::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); diff --git a/components/esm/loaddoor.hpp b/components/esm/loaddoor.hpp index 77ffc64899..2b927c56e0 100644 --- a/components/esm/loaddoor.hpp +++ b/components/esm/loaddoor.hpp @@ -14,7 +14,7 @@ struct Door std::string mId, mName, mModel, mScript, mOpenSound, mCloseSound; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadench.cpp b/components/esm/loadench.cpp index c4e278368e..4b4c3a1ec5 100644 --- a/components/esm/loadench.cpp +++ b/components/esm/loadench.cpp @@ -12,7 +12,7 @@ void Enchantment::load(ESMReader &esm) mEffects.load(esm); } -void Enchantment::save(ESMWriter &esm) +void Enchantment::save(ESMWriter &esm) const { esm.writeHNT("ENDT", mData, 16); mEffects.save(esm); diff --git a/components/esm/loadench.hpp b/components/esm/loadench.hpp index 999f93ad97..3cdc3a0bd0 100644 --- a/components/esm/loadench.hpp +++ b/components/esm/loadench.hpp @@ -39,7 +39,7 @@ struct Enchantment EffectList mEffects; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; }; } #endif diff --git a/components/esm/loadfact.cpp b/components/esm/loadfact.cpp index e2712d462d..c8be518028 100644 --- a/components/esm/loadfact.cpp +++ b/components/esm/loadfact.cpp @@ -47,7 +47,7 @@ void Faction::load(ESMReader &esm) mReactions.push_back(r); } } -void Faction::save(ESMWriter &esm) +void Faction::save(ESMWriter &esm) const { esm.writeHNCString("FNAM", mName); @@ -61,7 +61,7 @@ void Faction::save(ESMWriter &esm) esm.writeHNT("FADT", mData, 240); - for (std::vector::iterator it = mReactions.begin(); it != mReactions.end(); ++it) + for (std::vector::const_iterator it = mReactions.begin(); it != mReactions.end(); ++it) { esm.writeHNString("ANAM", it->mFaction); esm.writeHNT("INTV", it->mReaction); diff --git a/components/esm/loadfact.hpp b/components/esm/loadfact.hpp index 891b996473..11f65a87f5 100644 --- a/components/esm/loadfact.hpp +++ b/components/esm/loadfact.hpp @@ -63,7 +63,7 @@ struct Faction std::string mRanks[10]; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadglob.cpp b/components/esm/loadglob.cpp index ccb519acd4..e1c2d44087 100644 --- a/components/esm/loadglob.cpp +++ b/components/esm/loadglob.cpp @@ -7,7 +7,7 @@ namespace ESM mValue.read (esm, ESM::Variant::Format_Global); } - void Global::save (ESMWriter &esm) + void Global::save (ESMWriter &esm) const { mValue.write (esm, ESM::Variant::Format_Global); } diff --git a/components/esm/loadglob.hpp b/components/esm/loadglob.hpp index 72e16c0ce5..06ff97ef20 100644 --- a/components/esm/loadglob.hpp +++ b/components/esm/loadglob.hpp @@ -21,7 +21,7 @@ struct Global Variant mValue; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadgmst.cpp b/components/esm/loadgmst.cpp index fe1cc1b047..3a7df45065 100644 --- a/components/esm/loadgmst.cpp +++ b/components/esm/loadgmst.cpp @@ -7,7 +7,7 @@ namespace ESM mValue.read (esm, ESM::Variant::Format_Gmst); } - void GameSetting::save (ESMWriter &esm) + void GameSetting::save (ESMWriter &esm) const { mValue.write (esm, ESM::Variant::Format_Gmst); } diff --git a/components/esm/loadgmst.hpp b/components/esm/loadgmst.hpp index a6e0c2ecbe..9c37c7da09 100644 --- a/components/esm/loadgmst.hpp +++ b/components/esm/loadgmst.hpp @@ -24,7 +24,7 @@ struct GameSetting void load(ESMReader &esm); - /// \todo remove the get* functions (redundant, since mValue as equivalent functions now). + /// \todo remove the get* functions (redundant, since mValue has equivalent functions now). int getInt() const; ///< Throws an exception if GMST is not of type int or float. @@ -35,7 +35,7 @@ struct GameSetting std::string getString() const; ///< Throwns an exception if GMST is not of type string. - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadinfo.cpp b/components/esm/loadinfo.cpp index 90f8fcf35b..1985da2cd9 100644 --- a/components/esm/loadinfo.cpp +++ b/components/esm/loadinfo.cpp @@ -120,7 +120,7 @@ void DialInfo::load(ESMReader &esm) esm.skipRecord(); } -void DialInfo::save(ESMWriter &esm) +void DialInfo::save(ESMWriter &esm) const { esm.writeHNCString("INAM", mId); esm.writeHNCString("PNAM", mPrev); @@ -135,7 +135,7 @@ void DialInfo::save(ESMWriter &esm) esm.writeHNOCString("SNAM", mSound); esm.writeHNOString("NAME", mResponse); - for (std::vector::iterator it = mSelects.begin(); it != mSelects.end(); ++it) + for (std::vector::const_iterator it = mSelects.begin(); it != mSelects.end(); ++it) { esm.writeHNString("SCVR", it->mSelectRule); it->mValue.write (esm, Variant::Format_Info); diff --git a/components/esm/loadinfo.hpp b/components/esm/loadinfo.hpp index 2361ed9eb5..351768e96f 100644 --- a/components/esm/loadinfo.hpp +++ b/components/esm/loadinfo.hpp @@ -99,7 +99,7 @@ struct DialInfo }; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; }; } diff --git a/components/esm/loadingr.cpp b/components/esm/loadingr.cpp index 7e31a4116d..1bc9ae41c6 100644 --- a/components/esm/loadingr.cpp +++ b/components/esm/loadingr.cpp @@ -37,7 +37,7 @@ void Ingredient::load(ESMReader &esm) } } -void Ingredient::save(ESMWriter &esm) +void Ingredient::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); esm.writeHNCString("FNAM", mName); diff --git a/components/esm/loadingr.hpp b/components/esm/loadingr.hpp index 5e286535f4..03e67924c5 100644 --- a/components/esm/loadingr.hpp +++ b/components/esm/loadingr.hpp @@ -28,7 +28,7 @@ struct Ingredient std::string mId, mName, mModel, mIcon, mScript; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadland.cpp b/components/esm/loadland.cpp index 60c475040f..8e54bcc5c9 100644 --- a/components/esm/loadland.cpp +++ b/components/esm/loadland.cpp @@ -16,14 +16,14 @@ void Land::LandData::save(ESMWriter &esm) offsets.mHeightOffset = mHeights[0] / HEIGHT_SCALE; offsets.mUnk1 = mUnk1; offsets.mUnk2 = mUnk2; - + float prevY = mHeights[0], prevX; int number = 0; // avoid multiplication for (int i = 0; i < LAND_SIZE; ++i) { float diff = (mHeights[number] - prevY) / HEIGHT_SCALE; offsets.mHeightData[number] = (diff >= 0) ? (int8_t) (diff + 0.5) : (int8_t) (diff - 0.5); - + prevX = prevY = mHeights[number]; ++number; @@ -132,7 +132,7 @@ void Land::load(ESMReader &esm) mLandData = NULL; } -void Land::save(ESMWriter &esm) +void Land::save(ESMWriter &esm) const { esm.startSubRecord("INTV"); esm.writeT(mX); @@ -140,18 +140,6 @@ void Land::save(ESMWriter &esm) esm.endRecord("INTV"); esm.writeHNT("DATA", mFlags); - - // TODO: Land! - bool wasLoaded = mDataLoaded; - if (mDataTypes) { - // Try to load all available data before saving - loadData(mDataTypes); - } - if (mDataLoaded) - mLandData->save(esm); - - if (!wasLoaded) - unloadData(); // Don't need to keep the data loaded if it wasn't already } /// \todo remove memory allocation when only defaults needed diff --git a/components/esm/loadland.hpp b/components/esm/loadland.hpp index 9c1fd1f5c6..3d3bcd67bb 100644 --- a/components/esm/loadland.hpp +++ b/components/esm/loadland.hpp @@ -94,7 +94,7 @@ struct Land LandData *mLandData; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; /** * Actually loads data diff --git a/components/esm/loadlevlist.cpp b/components/esm/loadlevlist.cpp index b54a912760..ab3f5e9e66 100644 --- a/components/esm/loadlevlist.cpp +++ b/components/esm/loadlevlist.cpp @@ -33,13 +33,13 @@ void LeveledListBase::load(ESMReader &esm) esm.getHNT(li.mLevel, "INTV"); } } -void LeveledListBase::save(ESMWriter &esm) +void LeveledListBase::save(ESMWriter &esm) const { esm.writeHNT("DATA", mFlags); esm.writeHNT("NNAM", mChanceNone); esm.writeHNT("INDX", mList.size()); - for (std::vector::iterator it = mList.begin(); it != mList.end(); ++it) + for (std::vector::const_iterator it = mList.begin(); it != mList.end(); ++it) { esm.writeHNCString(mRecName, it->mId); esm.writeHNT("INTV", it->mLevel); diff --git a/components/esm/loadlevlist.hpp b/components/esm/loadlevlist.hpp index 7339cac56f..f5fb7fd5b1 100644 --- a/components/esm/loadlevlist.hpp +++ b/components/esm/loadlevlist.hpp @@ -51,7 +51,7 @@ struct LeveledListBase std::vector mList; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadligh.cpp b/components/esm/loadligh.cpp index 89a2b8c65b..3f279c7c83 100644 --- a/components/esm/loadligh.cpp +++ b/components/esm/loadligh.cpp @@ -16,7 +16,7 @@ void Light::load(ESMReader &esm) mScript = esm.getHNOString("SCRI"); mSound = esm.getHNOString("SNAM"); } -void Light::save(ESMWriter &esm) +void Light::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); diff --git a/components/esm/loadligh.hpp b/components/esm/loadligh.hpp index 3f0b76d6e2..9a341f0de5 100644 --- a/components/esm/loadligh.hpp +++ b/components/esm/loadligh.hpp @@ -44,7 +44,7 @@ struct Light std::string mSound, mScript, mModel, mIcon, mName, mId; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadlock.cpp b/components/esm/loadlock.cpp index 03eac52bd5..318769ec08 100644 --- a/components/esm/loadlock.cpp +++ b/components/esm/loadlock.cpp @@ -17,7 +17,7 @@ void Lockpick::load(ESMReader &esm) mIcon = esm.getHNOString("ITEX"); } -void Lockpick::save(ESMWriter &esm) +void Lockpick::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); esm.writeHNCString("FNAM", mName); diff --git a/components/esm/loadlock.hpp b/components/esm/loadlock.hpp index 953066cb2f..aea5a4f31d 100644 --- a/components/esm/loadlock.hpp +++ b/components/esm/loadlock.hpp @@ -24,7 +24,7 @@ struct Lockpick std::string mId, mName, mModel, mIcon, mScript; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadltex.cpp b/components/esm/loadltex.cpp index e523e9fa7f..dc1bc164b4 100644 --- a/components/esm/loadltex.cpp +++ b/components/esm/loadltex.cpp @@ -11,7 +11,7 @@ void LandTexture::load(ESMReader &esm) esm.getHNT(mIndex, "INTV"); mTexture = esm.getHNString("DATA"); } -void LandTexture::save(ESMWriter &esm) +void LandTexture::save(ESMWriter &esm) const { esm.writeHNT("INTV", mIndex); esm.writeHNCString("DATA", mTexture); diff --git a/components/esm/loadltex.hpp b/components/esm/loadltex.hpp index 6e6d987d49..3d08169484 100644 --- a/components/esm/loadltex.hpp +++ b/components/esm/loadltex.hpp @@ -31,7 +31,7 @@ struct LandTexture int mIndex; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; }; } #endif diff --git a/components/esm/loadmgef.cpp b/components/esm/loadmgef.cpp index 060645b5f4..9eaeff7046 100644 --- a/components/esm/loadmgef.cpp +++ b/components/esm/loadmgef.cpp @@ -58,15 +58,11 @@ void MagicEffect::load(ESMReader &esm) mDescription = esm.getHNOString("DESC"); } -void MagicEffect::save(ESMWriter &esm) +void MagicEffect::save(ESMWriter &esm) const { esm.writeHNT("INDX", mIndex); - mData.mFlags &= 0xe00; esm.writeHNT("MEDT", mData, 36); - if (mIndex>=0 && mIndex::iterator DestIter; + typedef std::vector::const_iterator DestIter; for (DestIter it = mTransport.begin(); it != mTransport.end(); ++it) { esm.writeHNT("DODT", it->mPos, sizeof(it->mPos)); esm.writeHNOCString("DNAM", it->mCellName); diff --git a/components/esm/loadnpc.hpp b/components/esm/loadnpc.hpp index 009bc5ef3b..009548c596 100644 --- a/components/esm/loadnpc.hpp +++ b/components/esm/loadnpc.hpp @@ -117,7 +117,7 @@ struct NPC std::string mHair, mHead; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; bool isMale() const; diff --git a/components/esm/loadnpcc.hpp b/components/esm/loadnpcc.hpp index 79d92397f8..f023fd2172 100644 --- a/components/esm/loadnpcc.hpp +++ b/components/esm/loadnpcc.hpp @@ -84,7 +84,7 @@ struct LoadNPCC { esm.skipRecord(); } - void save(ESMWriter &esm) + void save(ESMWriter &esm) const { } }; diff --git a/components/esm/loadpgrd.cpp b/components/esm/loadpgrd.cpp index 882addcb9d..65d1f80553 100644 --- a/components/esm/loadpgrd.cpp +++ b/components/esm/loadpgrd.cpp @@ -70,25 +70,25 @@ void Pathgrid::load(ESMReader &esm) } } } -void Pathgrid::save(ESMWriter &esm) +void Pathgrid::save(ESMWriter &esm) const { esm.writeHNT("DATA", mData, 12); esm.writeHNCString("NAME", mCell); - + if (!mPoints.empty()) { esm.startSubRecord("PGRP"); - for (PointList::iterator it = mPoints.begin(); it != mPoints.end(); ++it) + for (PointList::const_iterator it = mPoints.begin(); it != mPoints.end(); ++it) { esm.writeT(*it); } esm.endRecord("PGRP"); } - + if (!mEdges.empty()) { esm.startSubRecord("PGRC"); - for (std::vector::iterator it = mEdges.begin(); it != mEdges.end(); ++it) + for (std::vector::const_iterator it = mEdges.begin(); it != mEdges.end(); ++it) { esm.writeT(it->mV1); } diff --git a/components/esm/loadpgrd.hpp b/components/esm/loadpgrd.hpp index c3f50fc4da..d14433a786 100644 --- a/components/esm/loadpgrd.hpp +++ b/components/esm/loadpgrd.hpp @@ -46,7 +46,7 @@ struct Pathgrid EdgeList mEdges; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; }; } #endif diff --git a/components/esm/loadprob.cpp b/components/esm/loadprob.cpp index 729f8404e5..0fb4c97507 100644 --- a/components/esm/loadprob.cpp +++ b/components/esm/loadprob.cpp @@ -17,7 +17,7 @@ void Probe::load(ESMReader &esm) mIcon = esm.getHNOString("ITEX"); } -void Probe::save(ESMWriter &esm) +void Probe::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); esm.writeHNCString("FNAM", mName); diff --git a/components/esm/loadprob.hpp b/components/esm/loadprob.hpp index 55b896bcda..d0a8256ab6 100644 --- a/components/esm/loadprob.hpp +++ b/components/esm/loadprob.hpp @@ -24,7 +24,7 @@ struct Probe std::string mId, mName, mModel, mIcon, mScript; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadrace.cpp b/components/esm/loadrace.cpp index 955424e2b9..e9e1d0d797 100644 --- a/components/esm/loadrace.cpp +++ b/components/esm/loadrace.cpp @@ -22,7 +22,7 @@ void Race::load(ESMReader &esm) mPowers.load(esm); mDescription = esm.getHNOString("DESC"); } -void Race::save(ESMWriter &esm) +void Race::save(ESMWriter &esm) const { esm.writeHNCString("FNAM", mName); esm.writeHNT("RADT", mData, 140); diff --git a/components/esm/loadrace.hpp b/components/esm/loadrace.hpp index 6ecec8ebb9..a53a980701 100644 --- a/components/esm/loadrace.hpp +++ b/components/esm/loadrace.hpp @@ -65,7 +65,7 @@ struct Race SpellList mPowers; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadregn.cpp b/components/esm/loadregn.cpp index 41c7f507ae..fd42b9ee83 100644 --- a/components/esm/loadregn.cpp +++ b/components/esm/loadregn.cpp @@ -28,7 +28,7 @@ void Region::load(ESMReader &esm) mSoundList.push_back(sr); } } -void Region::save(ESMWriter &esm) +void Region::save(ESMWriter &esm) const { esm.writeHNCString("FNAM", mName); @@ -40,7 +40,7 @@ void Region::save(ESMWriter &esm) esm.writeHNOCString("BNAM", mSleepList); esm.writeHNT("CNAM", mMapColor); - for (std::vector::iterator it = mSoundList.begin(); it != mSoundList.end(); ++it) + for (std::vector::const_iterator it = mSoundList.begin(); it != mSoundList.end(); ++it) { esm.writeHNT("SNAM", *it); } diff --git a/components/esm/loadregn.hpp b/components/esm/loadregn.hpp index f2a3d9a108..a6075d65ae 100644 --- a/components/esm/loadregn.hpp +++ b/components/esm/loadregn.hpp @@ -47,7 +47,7 @@ struct Region std::vector mSoundList; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadrepa.cpp b/components/esm/loadrepa.cpp index ced6daa2e9..59bfa01695 100644 --- a/components/esm/loadrepa.cpp +++ b/components/esm/loadrepa.cpp @@ -17,7 +17,7 @@ void Repair::load(ESMReader &esm) mIcon = esm.getHNOString("ITEX"); } -void Repair::save(ESMWriter &esm) +void Repair::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); esm.writeHNCString("FNAM", mName); diff --git a/components/esm/loadrepa.hpp b/components/esm/loadrepa.hpp index 83812bad9c..771e7ead07 100644 --- a/components/esm/loadrepa.hpp +++ b/components/esm/loadrepa.hpp @@ -24,7 +24,7 @@ struct Repair std::string mId, mName, mModel, mIcon, mScript; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadscpt.cpp b/components/esm/loadscpt.cpp index 2c1b018d97..8afb85602d 100644 --- a/components/esm/loadscpt.cpp +++ b/components/esm/loadscpt.cpp @@ -50,11 +50,11 @@ void Script::load(ESMReader &esm) // Script text mScriptText = esm.getHNOString("SCTX"); } -void Script::save(ESMWriter &esm) +void Script::save(ESMWriter &esm) const { std::string varNameString; if (!mVarNames.empty()) - for (std::vector::iterator it = mVarNames.begin(); it != mVarNames.end(); ++it) + for (std::vector::const_iterator it = mVarNames.begin(); it != mVarNames.end(); ++it) varNameString.append(*it); SCHD data; @@ -68,7 +68,7 @@ void Script::save(ESMWriter &esm) if (!mVarNames.empty()) { esm.startSubRecord("SCVR"); - for (std::vector::iterator it = mVarNames.begin(); it != mVarNames.end(); ++it) + for (std::vector::const_iterator it = mVarNames.begin(); it != mVarNames.end(); ++it) { esm.writeHCString(*it); } diff --git a/components/esm/loadscpt.hpp b/components/esm/loadscpt.hpp index be7e839002..450224faa4 100644 --- a/components/esm/loadscpt.hpp +++ b/components/esm/loadscpt.hpp @@ -56,7 +56,7 @@ public: std::string mScriptText; // Uncompiled script void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadskil.cpp b/components/esm/loadskil.cpp index 676a835c3b..f6a2c49503 100644 --- a/components/esm/loadskil.cpp +++ b/components/esm/loadskil.cpp @@ -137,7 +137,7 @@ void Skill::load(ESMReader &esm) mId = indexToId (mIndex); } -void Skill::save(ESMWriter &esm) +void Skill::save(ESMWriter &esm) const { esm.writeHNT("INDX", mIndex); esm.writeHNT("SKDT", mData, 24); diff --git a/components/esm/loadskil.hpp b/components/esm/loadskil.hpp index 384f874545..2436173cbb 100644 --- a/components/esm/loadskil.hpp +++ b/components/esm/loadskil.hpp @@ -75,7 +75,7 @@ struct Skill static const boost::array sSkillIds; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadsndg.cpp b/components/esm/loadsndg.cpp index 42d524226d..9b992c9606 100644 --- a/components/esm/loadsndg.cpp +++ b/components/esm/loadsndg.cpp @@ -13,7 +13,7 @@ void SoundGenerator::load(ESMReader &esm) mCreature = esm.getHNOString("CNAM"); mSound = esm.getHNOString("SNAM"); } -void SoundGenerator::save(ESMWriter &esm) +void SoundGenerator::save(ESMWriter &esm) const { esm.writeHNT("DATA", mType, 4); esm.writeHNOCString("CNAM", mCreature); diff --git a/components/esm/loadsndg.hpp b/components/esm/loadsndg.hpp index a6226c1545..2756676ef1 100644 --- a/components/esm/loadsndg.hpp +++ b/components/esm/loadsndg.hpp @@ -33,7 +33,7 @@ struct SoundGenerator std::string mId, mCreature, mSound; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; }; } #endif diff --git a/components/esm/loadsoun.cpp b/components/esm/loadsoun.cpp index 07af2b5e91..0f6b0f84a7 100644 --- a/components/esm/loadsoun.cpp +++ b/components/esm/loadsoun.cpp @@ -17,7 +17,7 @@ void Sound::load(ESMReader &esm) << endl; */ } -void Sound::save(ESMWriter &esm) +void Sound::save(ESMWriter &esm) const { esm.writeHNCString("FNAM", mSound); esm.writeHNT("DATA", mData, 3); diff --git a/components/esm/loadsoun.hpp b/components/esm/loadsoun.hpp index f8e38ac092..6c9bb1fed3 100644 --- a/components/esm/loadsoun.hpp +++ b/components/esm/loadsoun.hpp @@ -20,7 +20,7 @@ struct Sound std::string mId, mSound; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadspel.cpp b/components/esm/loadspel.cpp index 8149fe4cef..5c0bd956f7 100644 --- a/components/esm/loadspel.cpp +++ b/components/esm/loadspel.cpp @@ -13,7 +13,7 @@ void Spell::load(ESMReader &esm) mEffects.load(esm); } -void Spell::save(ESMWriter &esm) +void Spell::save(ESMWriter &esm) const { esm.writeHNOCString("FNAM", mName); esm.writeHNT("SPDT", mData, 12); diff --git a/components/esm/loadspel.hpp b/components/esm/loadspel.hpp index 3a620962d1..b34bd29f18 100644 --- a/components/esm/loadspel.hpp +++ b/components/esm/loadspel.hpp @@ -42,7 +42,7 @@ struct Spell EffectList mEffects; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadsscr.cpp b/components/esm/loadsscr.cpp index ae50de517c..f51b7be479 100644 --- a/components/esm/loadsscr.cpp +++ b/components/esm/loadsscr.cpp @@ -11,7 +11,7 @@ void StartScript::load(ESMReader &esm) mData = esm.getHNString("DATA"); mScript = esm.getHNString("NAME"); } -void StartScript::save(ESMWriter &esm) +void StartScript::save(ESMWriter &esm) const { esm.writeHNString("DATA", mData); esm.writeHNString("NAME", mScript); diff --git a/components/esm/loadsscr.hpp b/components/esm/loadsscr.hpp index 713fe96b52..2326f00f43 100644 --- a/components/esm/loadsscr.hpp +++ b/components/esm/loadsscr.hpp @@ -24,7 +24,7 @@ struct StartScript // Load a record and add it to the list void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; }; } diff --git a/components/esm/loadstat.cpp b/components/esm/loadstat.cpp index c9346dafca..38206422bb 100644 --- a/components/esm/loadstat.cpp +++ b/components/esm/loadstat.cpp @@ -10,7 +10,7 @@ void Static::load(ESMReader &esm) { mModel = esm.getHNString("MODL"); } -void Static::save(ESMWriter &esm) +void Static::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); } diff --git a/components/esm/loadstat.hpp b/components/esm/loadstat.hpp index 1adb7d05be..df42c0c491 100644 --- a/components/esm/loadstat.hpp +++ b/components/esm/loadstat.hpp @@ -25,7 +25,7 @@ struct Static std::string mId, mModel; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadweap.cpp b/components/esm/loadweap.cpp index 2537123969..e21d8924a1 100644 --- a/components/esm/loadweap.cpp +++ b/components/esm/loadweap.cpp @@ -15,7 +15,7 @@ void Weapon::load(ESMReader &esm) mIcon = esm.getHNOString("ITEX"); mEnchant = esm.getHNOString("ENAM"); } -void Weapon::save(ESMWriter &esm) +void Weapon::save(ESMWriter &esm) const { esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); diff --git a/components/esm/loadweap.hpp b/components/esm/loadweap.hpp index b62179ccb1..42810d3afb 100644 --- a/components/esm/loadweap.hpp +++ b/components/esm/loadweap.hpp @@ -59,7 +59,7 @@ struct Weapon std::string mId, mName, mModel, mIcon, mEnchant, mScript; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/spelllist.cpp b/components/esm/spelllist.cpp index dd886cf7ff..24d3c3d0a5 100644 --- a/components/esm/spelllist.cpp +++ b/components/esm/spelllist.cpp @@ -12,9 +12,9 @@ void SpellList::load(ESMReader &esm) } } -void SpellList::save(ESMWriter &esm) +void SpellList::save(ESMWriter &esm) const { - for (std::vector::iterator it = mList.begin(); it != mList.end(); ++it) { + for (std::vector::const_iterator it = mList.begin(); it != mList.end(); ++it) { esm.writeHNString("NPCS", *it, 32); } } diff --git a/components/esm/spelllist.hpp b/components/esm/spelllist.hpp index 52999270a0..934bdda7ad 100644 --- a/components/esm/spelllist.hpp +++ b/components/esm/spelllist.hpp @@ -17,7 +17,7 @@ namespace ESM std::vector mList; void load(ESMReader &esm); - void save(ESMWriter &esm); + void save(ESMWriter &esm) const; }; } From bf0fba68af5926ee3abd03ce77d43132315ba6f9 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 16 Sep 2013 12:51:57 +0200 Subject: [PATCH 039/434] added save stage for globals --- apps/opencs/model/doc/document.cpp | 6 +-- apps/opencs/model/doc/saving.cpp | 9 +++- apps/opencs/model/doc/savingstages.hpp | 62 ++++++++++++++++++++++++++ components/esm/esmwriter.hpp | 2 +- 4 files changed, 74 insertions(+), 5 deletions(-) diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index 9b22175591..525f18a206 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -2058,9 +2058,9 @@ void CSMDoc::Document::addOptionalGlobals() { static const char *sGlobals[] = { - "dayspassed", - "pcwerewolf", - "pcyear", + "DaysPassed", + "PCWerewolf", + "PCYear", 0 }; diff --git a/apps/opencs/model/doc/saving.cpp b/apps/opencs/model/doc/saving.cpp index 67073ca434..e0180bee4e 100644 --- a/apps/opencs/model/doc/saving.cpp +++ b/apps/opencs/model/doc/saving.cpp @@ -1,9 +1,14 @@ #include "saving.hpp" -#include "state.hpp" +#include +#include "../world/data.hpp" +#include "../world/idcollection.hpp" + +#include "state.hpp" #include "savingstages.hpp" +#include "document.hpp" CSMDoc::Saving::Saving (Document& document) : Operation (State_Saving, true, true), mDocument (document), mState (*this) @@ -12,6 +17,8 @@ CSMDoc::Saving::Saving (Document& document) appendStage (new WriteHeaderStage (mDocument, mState)); + appendStage (new WriteCollectionStage > + (mDocument.getData().getGlobals(), mState, ESM::REC_GLOB)); appendStage (new CloseSaveStage (mState)); diff --git a/apps/opencs/model/doc/savingstages.hpp b/apps/opencs/model/doc/savingstages.hpp index 914a2d585a..9787679c6f 100644 --- a/apps/opencs/model/doc/savingstages.hpp +++ b/apps/opencs/model/doc/savingstages.hpp @@ -1,8 +1,14 @@ #ifndef CSM_DOC_SAVINGSTAGES_H #define CSM_DOC_SAVINGSTAGES_H +#include + #include "stage.hpp" +#include "savingstate.hpp" + +#include "../world/record.hpp" + namespace CSMDoc { class Document; @@ -40,6 +46,62 @@ namespace CSMDoc ///< Messages resulting from this stage will be appended to \a messages. }; + + template + class WriteCollectionStage : public Stage + { + const CollectionT& mCollection; + SavingState& mState; + ESM::RecNameInts mRecordType; + + public: + + WriteCollectionStage (const CollectionT& collection, SavingState& state, + ESM::RecNameInts recordType); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, std::vector& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; + + template + WriteCollectionStage::WriteCollectionStage (const CollectionT& collection, + SavingState& state, ESM::RecNameInts recordType) + : mCollection (collection), mState (state), mRecordType (recordType) + {} + + template + int WriteCollectionStage::setup() + { + return mCollection.getSize(); + } + + template + void WriteCollectionStage::perform (int stage, std::vector& messages) + { + CSMWorld::RecordBase::State state = mCollection.getRecord (stage).mState; + + if (state==CSMWorld::RecordBase::State_Modified || + state==CSMWorld::RecordBase::State_ModifiedOnly) + { + std::string type; + for (int i=0; i<4; ++i) + /// \todo make endianess agnostic (change ESMWriter interface?) + type += reinterpret_cast (&mRecordType)[i]; + + mState.getWriter().startRecord (type); + mCollection.getRecord (stage).mModified.save (mState.getWriter()); + mState.getWriter().endRecord (type); + } + else if (state==CSMWorld::RecordBase::State_Deleted) + { + /// \todo write record with delete flag + } + } + + class CloseSaveStage : public Stage { SavingState& mState; diff --git a/components/esm/esmwriter.hpp b/components/esm/esmwriter.hpp index b0925463a0..fc64c4a137 100644 --- a/components/esm/esmwriter.hpp +++ b/components/esm/esmwriter.hpp @@ -90,7 +90,7 @@ class ESMWriter write((char*)&data, size); } - void startRecord(const std::string& name, uint32_t flags); + void startRecord(const std::string& name, uint32_t flags = 0); void startSubRecord(const std::string& name); void endRecord(const std::string& name); void writeHString(const std::string& data); From 03054c816071bd53800d59c95170b18dcc8c8241 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 16 Sep 2013 14:10:05 +0200 Subject: [PATCH 040/434] forgot to write record ID --- apps/opencs/model/doc/savingstages.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/opencs/model/doc/savingstages.hpp b/apps/opencs/model/doc/savingstages.hpp index 9787679c6f..96b1fe17f6 100644 --- a/apps/opencs/model/doc/savingstages.hpp +++ b/apps/opencs/model/doc/savingstages.hpp @@ -92,6 +92,7 @@ namespace CSMDoc type += reinterpret_cast (&mRecordType)[i]; mState.getWriter().startRecord (type); + mState.getWriter().writeHNCString ("NAME", mCollection.getId (stage)); mCollection.getRecord (stage).mModified.save (mState.getWriter()); mState.getWriter().endRecord (type); } From acfd78c62ad6ee84c7160e1772dcdc19fd96d831 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 16 Sep 2013 14:17:04 +0200 Subject: [PATCH 041/434] implemented saving for all supported record types except cells, referencables and references --- apps/opencs/model/doc/saving.cpp | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/apps/opencs/model/doc/saving.cpp b/apps/opencs/model/doc/saving.cpp index e0180bee4e..7e0b10d66b 100644 --- a/apps/opencs/model/doc/saving.cpp +++ b/apps/opencs/model/doc/saving.cpp @@ -20,6 +20,36 @@ CSMDoc::Saving::Saving (Document& document) appendStage (new WriteCollectionStage > (mDocument.getData().getGlobals(), mState, ESM::REC_GLOB)); + appendStage (new WriteCollectionStage > + (mDocument.getData().getGmsts(), mState, ESM::REC_GMST)); + + appendStage (new WriteCollectionStage > + (mDocument.getData().getSkills(), mState, ESM::REC_SKIL)); + + appendStage (new WriteCollectionStage > + (mDocument.getData().getClasses(), mState, ESM::REC_CLAS)); + + appendStage (new WriteCollectionStage > + (mDocument.getData().getFactions(), mState, ESM::REC_FACT)); + + appendStage (new WriteCollectionStage > + (mDocument.getData().getRaces(), mState, ESM::REC_RACE)); + + appendStage (new WriteCollectionStage > + (mDocument.getData().getSounds(), mState, ESM::REC_SOUN)); + + appendStage (new WriteCollectionStage > + (mDocument.getData().getScripts(), mState, ESM::REC_SCPT)); + + appendStage (new WriteCollectionStage > + (mDocument.getData().getRegions(), mState, ESM::REC_REGN)); + + appendStage (new WriteCollectionStage > + (mDocument.getData().getBirthsigns(), mState, ESM::REC_BSGN)); + + appendStage (new WriteCollectionStage > + (mDocument.getData().getSpells(), mState, ESM::REC_SPEL)); + appendStage (new CloseSaveStage (mState)); From 0eb06ada39ca9ca8857325e816b61048348d896c Mon Sep 17 00:00:00 2001 From: graffy76 Date: Wed, 18 Sep 2013 02:36:23 -0500 Subject: [PATCH 042/434] Implemneting drag and drop --- apps/launcher/datafilespage.cpp | 32 +- apps/launcher/datafilespage.hpp | 1 + components/CMakeLists.txt | 2 +- components/esxselector/model/contentmodel.cpp | 343 ++++++---- components/esxselector/model/contentmodel.hpp | 9 +- .../esxselector/model/datafilesmodel.cpp | 612 ++++++++++-------- components/esxselector/model/esmfile.cpp | 64 +- components/esxselector/model/esmfile.hpp | 28 +- .../esxselector/model/masterproxymodel.cpp | 31 - .../esxselector/model/pluginsproxymodel.cpp | 26 - .../esxselector/view/contentselector.cpp | 150 ++--- .../esxselector/view/contentselector.hpp | 13 +- 12 files changed, 725 insertions(+), 586 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 070f455e4e..7069737eff 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -7,7 +7,6 @@ #include -#include #include #include @@ -20,6 +19,7 @@ #include "settings/launchersettings.hpp" #include "utils/textinputdialog.hpp" +#include "components/esxselector/view/contentselector.hpp" #include @@ -27,8 +27,8 @@ DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gam : mCfgMgr(cfg) , mGameSettings(gameSettings) , mLauncherSettings(launcherSettings) - , ContentSelector(parent) { + mContentSelector.setParent(parent); QMetaObject::connectSlotsByName(this); projectGroupBox->hide(); @@ -51,24 +51,21 @@ void DataFilesPage::createActions() void DataFilesPage::setupDataFiles() { - if (!mDataFilesModel) - qDebug() << "data files model undefined"; - // Set the encoding to the one found in openmw.cfg or the default - mDataFilesModel->setEncoding(mGameSettings.value(QString("encoding"), QString("win1252"))); + mContentSelector.setEncoding(mGameSettings.value(QString("encoding"), QString("win1252"))); QStringList paths = mGameSettings.getDataDirs(); foreach (const QString &path, paths) { - mDataFilesModel->addFiles(path); + mContentSelector.addFiles(path); } QString dataLocal = mGameSettings.getDataLocal(); if (!dataLocal.isEmpty()) - mDataFilesModel->addFiles(dataLocal); + mContentSelector.addFiles(dataLocal); // Sort by date accessed for now - mDataFilesModel->sort(3); + //mContentSelector->sort(3); QStringList profiles = mLauncherSettings.subKeys(QString("Profiles/")); QString profile = mLauncherSettings.value(QString("Profiles/currentprofile")); @@ -107,11 +104,11 @@ void DataFilesPage::loadSettings() if (profile.isEmpty()) return; - mDataFilesModel->uncheckAll(); + // mContentSelector.uncheckAll(); QStringList masters = mLauncherSettings.values(QString("Profiles/") + profile + QString("/master"), Qt::MatchExactly); QStringList plugins = mLauncherSettings.values(QString("Profiles/") + profile + QString("/plugin"), Qt::MatchExactly); - +/* foreach (const QString &master, masters) { QModelIndex index = mDataFilesModel->indexFromItem(mDataFilesModel->findItem(master)); if (index.isValid()) @@ -123,12 +120,13 @@ void DataFilesPage::loadSettings() if (index.isValid()) mDataFilesModel->setCheckState(index, Qt::Checked); } + */ } void DataFilesPage::saveSettings() { - if (mDataFilesModel->rowCount() < 1) - return; +// if (mDataFilesModel->rowCount() < 1) +// return; QString profile = mLauncherSettings.value(QString("Profiles/currentprofile")); @@ -143,8 +141,8 @@ void DataFilesPage::saveSettings() mGameSettings.remove(QString("master")); mGameSettings.remove(QString("plugin")); - EsxModel::EsmFileList items = mDataFilesModel->checkedItems(); - + // EsxModel::EsmFileList items = mDataFilesModel->checkedItems(); +/* foreach(const EsxModel::EsmFile *item, items) { if (item->masters().size() == 0) { @@ -156,7 +154,7 @@ void DataFilesPage::saveSettings() mGameSettings.setMultiValue(QString("plugin"), item->fileName()); } } - +*/ } void DataFilesPage::updateOkButton(const QString &text) @@ -241,7 +239,7 @@ void DataFilesPage::setPluginsCheckstates(Qt::CheckState state) if (!sourceIndex.isValid()) return; - mDataFilesModel->setCheckState(sourceIndex, state); + //mDataFilesModel->setCheckState(sourceIndex, state); } } diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index f3792b1f13..ed92da749d 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -52,6 +52,7 @@ private slots: private: QMenu *mContextMenu; + ContentSelector mContentSelector; Files::ConfigurationManager &mCfgMgr; diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 0f7c5017b7..b79fa027ee 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -72,7 +72,7 @@ find_package(Qt4 COMPONENTS QtCore QtGui) if(QT_QTGUI_LIBRARY AND QT_QTCORE_LIBRARY) add_component_qt_dir (esxselector - model/masterproxymodel model/modelitem model/datafilesmodel + model/masterproxymodel model/modelitem model/pluginsproxymodel model/esmfile model/naturalsort model/contentmodel view/profilescombobox view/comboboxlineedit diff --git a/components/esxselector/model/contentmodel.cpp b/components/esxselector/model/contentmodel.cpp index 673665775a..bfbb1bef7a 100644 --- a/components/esxselector/model/contentmodel.cpp +++ b/components/esxselector/model/contentmodel.cpp @@ -21,20 +21,7 @@ int EsxModel::ContentModel::columnCount(const QModelIndex &parent) const return 1; } -/* -QModelIndex EsxModel::ContentModel::parent(const QModelIndex &child) const -{ - if(!child.isValid()) - return 0; - return child.parent(); -} - -QModelIndex EsxModel::ContentModel::index(int row, int column, const QModelIndex &parent) const -{ - -} -*/ int EsxModel::ContentModel::rowCount(const QModelIndex &parent) const { if(parent.isValid()) @@ -43,6 +30,53 @@ int EsxModel::ContentModel::rowCount(const QModelIndex &parent) const return mFiles.size(); } +EsxModel::EsmFile* EsxModel::ContentModel::item(int row) const +{ + if (row >= 0 && row < mFiles.count()) + return mFiles.at(row); + + return 0; +} + +EsxModel::EsmFile* EsxModel::ContentModel::findItem(const QString &name) +{ + for (int i = 0; i < mFiles.size(); ++i) + { + if (name == item(i)->fileName()) + return item(i); + } + + return 0; +} + +QModelIndex EsxModel::ContentModel::indexFromItem(EsmFile *item) const +{ + if (item) + return index(mFiles.indexOf(item),0); + + return QModelIndex(); +} + +Qt::ItemFlags EsxModel::ContentModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return Qt::NoItemFlags; + + EsmFile *file = item(index.row()); + + if (!file) + return Qt::NoItemFlags; + + Qt::ItemFlags dragDropFlags = Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; + Qt::ItemFlags checkFlags = Qt::ItemIsUserCheckable; + Qt::ItemFlags defaultFlags = Qt::ItemIsDropEnabled | Qt::ItemIsSelectable; + + if (canBeChecked(file)) + return Qt::ItemIsEnabled | dragDropFlags | checkFlags | defaultFlags; + else + return defaultFlags; +} + QVariant EsxModel::ContentModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) @@ -70,18 +104,14 @@ QVariant EsxModel::ContentModel::data(const QModelIndex &index, int role) const case 1: return file->author(); case 2: - return QString("%1 kB").arg(int((file->size() + 1023) / 1024)); - case 3: return file->modified().toString(Qt::ISODate); - case 4: - return file->accessed().toString(Qt::TextDate); - case 5: + case 3: return file->version(); - case 6: + case 4: return file->path(); - case 7: + case 5: return file->masters().join(", "); - case 8: + case 6: return file->description(); } @@ -97,8 +127,6 @@ QVariant EsxModel::ContentModel::data(const QModelIndex &index, int role) const return Qt::AlignLeft + Qt::AlignVCenter; case 2: case 3: - case 4: - case 5: return Qt::AlignRight + Qt::AlignVCenter; default: return Qt::AlignLeft + Qt::AlignVCenter; @@ -124,47 +152,88 @@ QVariant EsxModel::ContentModel::data(const QModelIndex &index, int role) const .arg(file->masters().join(", ")); } + case Qt::CheckStateRole: + if (!file->isMaster()) + return isChecked(file->fileName()); + break; + case Qt::UserRole: { - if (file->masters().size() == 0) + if (file->isMaster()) return "game"; else return "addon"; } - default: - return QVariant(); + case Qt::UserRole + 1: + return isChecked(file->fileName()); + break; } -} - -Qt::ItemFlags EsxModel::ContentModel::flags(const QModelIndex &index) const -{ - if (!index.isValid()) - return Qt::NoItemFlags; - - EsmFile *file = item(index.row()); - - if (!file) - return Qt::NoItemFlags; - - Qt::ItemFlags dragDropFlags = Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; - Qt::ItemFlags checkFlags = Qt::ItemIsUserCheckable; - Qt::ItemFlags defaultFlags = Qt::ItemIsDropEnabled | Qt::ItemIsSelectable; - - if (canBeChecked(file)) - return Qt::ItemIsEnabled | dragDropFlags | checkFlags | defaultFlags; - else - return defaultFlags; + return QVariant(); } bool EsxModel::ContentModel::setData(const QModelIndex &index, const QVariant &value, int role) { - if (index.isValid() && role == Qt::EditRole) + if(!index.isValid()) + return false; + + EsmFile *file = item(index.row()); + QString fileName = file->fileName(); + + switch(role) { - QString fname = value.value(); - mFiles.replace(index.row(), findItem(fname)); - emit dataChanged(index, index); - return true; + case Qt::EditRole: + { + QStringList list = value.toStringList(); + + //iterate the string list, assigning values to proeprties + //index-enum correspondence 1:1 + for (int i = 0; i < EsxModel::Property_Master; i++) + file->setProperty(static_cast(i), list.at(i)); + + //iterate the remainder of the string list, assifning everything + // as + for (int i = EsxModel::Property_Master; i < list.size(); i++) + file->setProperty (EsxModel::Property_Master, list.at(i)); + + //emit data changed for the item itself + emit dataChanged(index, index); + + return true; + } + break; + + case Qt::UserRole+1: + { + setCheckState(fileName, value.toBool()); + + emit dataChanged(index, index); + + for(int i = 0; i < mFiles.size(); i++) + { + + if (mFiles.at(i)->masters().contains(fileName)) + { + QModelIndex idx = QAbstractTableModel::index(i, 0); + emit dataChanged(idx, idx); + } + } + + return true; + } + break; + + case Qt::CheckStateRole: + { + bool checked = ((value.toInt() == Qt::Checked) && !isChecked(fileName)); + + setCheckState(fileName, checked); + + emit dataChanged(index, index); + + return true; + } + break; } return false; @@ -172,24 +241,31 @@ bool EsxModel::ContentModel::setData(const QModelIndex &index, const QVariant &v bool EsxModel::ContentModel::insertRows(int position, int rows, const QModelIndex &parent) { + if (parent.isValid()) + return false; + beginInsertRows(parent, position, position+rows-1); + { + for (int row = 0; row < rows; ++row) + mFiles.insert(position, new EsmFile); - for (int row = 0; row < rows; ++row) - mFiles.insert(position, new EsmFile); + } endInsertRows(); - endInsertRows(); return true; } bool EsxModel::ContentModel::removeRows(int position, int rows, const QModelIndex &parent) { + if (parent.isValid()) + return false; + beginRemoveRows(parent, position, position+rows-1); + { + for (int row = 0; row < rows; ++row) + delete mFiles.takeAt(position); - for (int row = 0; row < rows; ++row) - mFiles.removeAt(position); + } endRemoveRows(); - endRemoveRows(); - emit dataChanged(index(0,0,parent), index(rowCount()-1, 0, parent)); return true; } @@ -201,27 +277,30 @@ Qt::DropActions EsxModel::ContentModel::supportedDropActions() const QStringList EsxModel::ContentModel::mimeTypes() const { QStringList types; + types << "application/omwcontent"; + return types; } QMimeData *EsxModel::ContentModel::mimeData(const QModelIndexList &indexes) const { - QMimeData *mimeData = new QMimeData(); QByteArray encodedData; - QDataStream stream(&encodedData, QIODevice::WriteOnly); - foreach (const QModelIndex &index, indexes) { - if (index.isValid()) - { - QString text = data(index, Qt::DisplayRole).toString(); - stream << text; - } + if (!index.isValid()) + continue; + + QByteArray fileData = item(index.row())->encodedData(); + + foreach (const char c, fileData) + encodedData.append(c); } + QMimeData *mimeData = new QMimeData(); mimeData->setData("application/omwcontent", encodedData); + return mimeData; } @@ -240,31 +319,66 @@ bool EsxModel::ContentModel::dropMimeData(const QMimeData *data, Qt::DropAction if (row != -1) beginRow = row; + else if (parent.isValid()) beginRow = parent.row(); + else beginRow = rowCount(); QByteArray encodedData = data->data("application/omwcontent"); QDataStream stream(&encodedData, QIODevice::ReadOnly); - QStringList newItems; - int rows = 0; while (!stream.atEnd()) { - QString text; - stream >> text; - newItems << text; - ++rows; + QStringList values; + + for (int i = 0; i < EsmFile::sPropertyCount; ++i) + stream >> values; + + insertRows(beginRow, 1); + + QModelIndex idx = index(beginRow++, 0, QModelIndex()); + setData(idx, values, Qt::EditRole); } - insertRows(beginRow, rows, QModelIndex()); + return true; +} - foreach (const QString &text, newItems) +bool EsxModel::ContentModel::canBeChecked(const EsmFile *file) const +{ + //element can be checked if all its dependencies are + foreach (const QString &master, file->masters()) { - QModelIndex idx = index(beginRow, 0, QModelIndex()); - setData(idx, text); - beginRow++; + {// if the master is not found in checkstates + // or it is not specifically checked, return false + if (!mCheckStates.contains(master)) + return false; + + if (!isChecked(master)) + return false; + } + + bool found = false; + + //iterate each file, if it is not a master and + //does not have a master that is currently checked, + //return false. + foreach(const EsmFile *file, mFiles) + { + QString filename = file->fileName(); + + found = (filename == master); + + if (found) + { + if (!isChecked(filename)) + return false; + } + } + + if (!found) + return false; } return true; @@ -272,9 +386,10 @@ bool EsxModel::ContentModel::dropMimeData(const QMimeData *data, Qt::DropAction void EsxModel::ContentModel::addFile(EsmFile *file) { - emit beginInsertRows(QModelIndex(), mFiles.count(), mFiles.count()); - mFiles.append(file); - emit endInsertRows(); + beginInsertRows(QModelIndex(), mFiles.count(), mFiles.count()); + { + mFiles.append(file); + } endInsertRows(); } void EsxModel::ContentModel::addFiles(const QString &path) @@ -319,9 +434,9 @@ void EsxModel::ContentModel::addFiles(const QString &path) } file->setAuthor(decoder->toUnicode(fileReader.getAuthor().c_str())); - file->setSize(info.size()); - file->setDates(info.lastModified(), info.lastRead()); - file->setVersion(fileReader.getFVer()); + //file->setSize(info.size()); + file->setDate(info.lastModified()); + file->setVersion(0.0f); file->setPath(info.absoluteFilePath()); file->setMasters(masters); file->setDescription(decoder->toUnicode(fileReader.getDesc().c_str())); @@ -342,66 +457,22 @@ void EsxModel::ContentModel::addFiles(const QString &path) delete decoder; } -EsxModel::EsmFile* EsxModel::ContentModel::findItem(const QString &name) +bool EsxModel::ContentModel::isChecked(const QString& name) const { - for (int i = 0; i < mFiles.size(); ++i) - { - if (name == item(i)->fileName()) - return item(i); - } - - // Not found - return 0; + return (mCheckStates[name] == Qt::Checked); } -EsxModel::EsmFile* EsxModel::ContentModel::item(int row) const +void EsxModel::ContentModel::setCheckState(const QString &name, bool isChecked) { - if (row >= 0 && row < mFiles.count()) - return mFiles.at(row); - - return 0; -} - -QModelIndex EsxModel::ContentModel::indexFromItem(EsmFile *item) const -{ - if (item) - //return createIndex(mFiles.indexOf(item), 0); - return index(mFiles.indexOf(item),0); - - return QModelIndex(); -} - -Qt::CheckState EsxModel::ContentModel::checkState(const QModelIndex &index) -{ - return mCheckStates[item(index.row())->fileName()]; -} - -void EsxModel::ContentModel::setCheckState(const QModelIndex &index, Qt::CheckState state) -{ - if (!index.isValid()) + if (name.isEmpty()) return; - QString name = item(index.row())->fileName(); + Qt::CheckState state = Qt::Unchecked; + + if (isChecked) + state = Qt::Checked; + mCheckStates[name] = state; - - // Force a redraw of the view since unchecking one item can affect another - QModelIndex firstIndex = indexFromItem(mFiles.first()); - QModelIndex lastIndex = indexFromItem(mFiles.last()); - - emit dataChanged(firstIndex, lastIndex); - //emit checkedItemsChanged(checkedItems()); - -} - -bool EsxModel::ContentModel::canBeChecked(const EsmFile *file) const -{ - //element can be checked if all its dependencies are - foreach (const QString &master, file->masters()) - { - if (!mCheckStates.contains(master) || mCheckStates[master] != Qt::Checked) - return false; - } - return true; } EsxModel::ContentFileList EsxModel::ContentModel::checkedItems() const diff --git a/components/esxselector/model/contentmodel.hpp b/components/esxselector/model/contentmodel.hpp index a585ab63b8..61c823ab38 100644 --- a/components/esxselector/model/contentmodel.hpp +++ b/components/esxselector/model/contentmodel.hpp @@ -37,14 +37,11 @@ namespace EsxModel QModelIndex indexFromItem(EsmFile *item) const; EsxModel::EsmFile *findItem(const QString &name); - Qt::CheckState checkState(const QModelIndex &index); - void setCheckState(const QModelIndex &index, Qt::CheckState state); + bool isChecked(const QString &name) const; + void setCheckState(const QString &name, bool isChecked); ContentFileList checkedItems() const; void uncheckAll(); -/* - QModelIndex index(int row, int column, const QModelIndex &parent) const; - QModelIndex parent(const QModelIndex &child) const; -*/ + private: void addFile(EsmFile *file); diff --git a/components/esxselector/model/datafilesmodel.cpp b/components/esxselector/model/datafilesmodel.cpp index ee940bb273..c98f70b162 100644 --- a/components/esxselector/model/datafilesmodel.cpp +++ b/components/esxselector/model/datafilesmodel.cpp @@ -24,6 +24,340 @@ EsxModel::DataFilesModel::~DataFilesModel() { } +int EsxModel::DataFilesModel::rowCount(const QModelIndex &parent) const +{ + return parent.isValid() ? 0 : mFiles.count(); +} + +int EsxModel::DataFilesModel::columnCount(const QModelIndex &parent) const +{ + return parent.isValid() ? 0 : 1; +} + +const EsxModel::EsmFile* EsxModel::DataFilesModel::findItem(const QString &name) +{ + for (int i = 0; i < mFiles.size(); ++i) + { + const EsmFile *file = item(i); + + if (name == file->fileName()) + return file; + } + + return 0; +} + +const EsxModel::EsmFile* EsxModel::DataFilesModel::item(int row) const +{ + if (row >= 0 && row < mFiles.count()) + return mFiles.at(row); + + return 0; +} + +Qt::ItemFlags EsxModel::DataFilesModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return Qt::NoItemFlags; + + const EsmFile *file = item(index.row()); + + if (!file) + return Qt::NoItemFlags; + + Qt::ItemFlags dragDropFlags = Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; + Qt::ItemFlags checkFlags = Qt::ItemIsUserCheckable | Qt::ItemIsEditable; + Qt::ItemFlags defaultFlags = Qt::ItemIsDropEnabled | Qt::ItemIsSelectable; + + if (canBeChecked(file)) + return defaultFlags | dragDropFlags | checkFlags | Qt::ItemIsEnabled; + else + return defaultFlags; +} + +QVariant EsxModel::DataFilesModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + if (index.row() >= mFiles.size()) + return QVariant(); + + const EsmFile *file = item(index.row()); + + if (!file) + return QVariant(); + + const int column = index.column(); + + switch (role) + { + case Qt::EditRole: + case Qt::DisplayRole: + { + + switch (column) + { + case 0: + return file->fileName(); + case 1: + return file->author(); + case 2: + return file->modified().toString(Qt::ISODate); + case 3: + return file->version(); + case 4: + return file->path(); + case 5: + return file->masters().join(", "); + case 6: + return file->description(); + } + break; + } + + case Qt::TextAlignmentRole: + { + switch (column) + { + case 0: + case 1: + return Qt::AlignLeft + Qt::AlignVCenter; + case 2: + case 3: + return Qt::AlignRight + Qt::AlignVCenter; + default: + return Qt::AlignLeft + Qt::AlignVCenter; + } + break; + } + + case Qt::ToolTipRole: + { + if (column != 0) + return QVariant(); + + if (file->version() == 0.0f) + return QVariant(); // Data not set + + QString tooltip = + QString("Author: %1
\ + Version: %2
\ +
Description:
%3
\ +
Dependencies: %4
") + .arg(file->author()) + .arg(QString::number(file->version())) + .arg(file->description()) + .arg(file->masters().join(", ")); + + + return tooltip; + break; + } + + case Qt::UserRole: + { + if (file->masters().size() == 0) + return "game"; + else + return "addon"; + + break; + } + + case Qt::UserRole + 1: + //return check state here + break; + + default: + return QVariant(); + break; + } + +} + +bool EsxModel::DataFilesModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid()) + return false; + + switch (role) + { + case Qt::EditRole: + { + const EsmFile *file = item(index.row()); + + // iterate loop to repopulate file pointer with data in string list. + QStringList list = value.toStringList(); + for (int i = 0; i <999; ++i) + { + file->setProperty(i, value.at(i)); + } + + //populate master list here (emit data changed for each master and + //each item (other than the dropped item) which share each of the masters + file->masters().append(masterList); + + emit dataChanged(index, index); + return true; + } + break; + + case Qt::UserRole + 1: + { + EsmFile *file = item(index.row()); + //set file's checkstate to the passed checkstate + emit dataChanged(index, index); + + for (int i = 0; i < mFiles.size(); ++i) + if (mFiles.at(i)->getMasters().contains(file->fileName())) + emit dataChanged(QAbstractTableModel::index(i,0), QAbstractTableModel::index(i,0)); + + return true; + } + break; + + case Qt::CheckStateRole: + { + EsmFile *file = item(index.row()); + + if ((value.toInt() == Qt::Checked) && !file->isChecked()) + file->setChecked(true); + else if (value.toInt() == Qt::Checked && file->isChecked()) + file->setChecked(false); + else if (value.toInt() == Qt::UnChecked) + file->setChecked(false); + + emit dataChanged(index, index); + + return true; + } + break; + } + return false; +} + +bool EsxModel::DataFilesModel::insertRows(int row, int count, const QModelIndex &parent) +{ + if (parent.isValid()) + return false; + + beginInsertRows(QModelIndex(),row, row+count-1); + { + for (int i = 0; i < count; ++i) + mFiles.insert(row, new EsmFile()); + } endInsertRows(); + + return true; +} + +bool EsxModel::DataFilesModel::removeRows(int row, int count, const QModelIndex &parent) +{ + if (parent.isValid()) + return false; + + beginRemoveRows(QModelIndex(), row, row+count-1); + { + for (int i = 0; i < count; ++i) + delete mFiles.takeAt(row); + } endRemoveRows(); + + return true; +} + +Qt::DropActions EsxModel::DataFilesModel::supportedDropActions() const +{ + return Qt::CopyAction | Qt::MoveAction; +} + +QStringList EsxModel::DataFilesModel::mimeTypes() const +{ + QStringList types; + types << "application/omwcontent"; + return types; +} + +QMimeData *EsxModel::DataFilesModel::mimeData(const QModelIndexList &indexes) const +{ + QMimeData *mimeData = new QMimeData(); + QByteArray encodedData; + + QDataStream stream (&encodedData, QIODevice::WriteOnly); + + foreach (const QModelIndex &index, indexes) + { + if (index.isValid()) + { + EsmFile *file = item (index.row()); + + for (int i = 0; i < file->propertyCount(); ++i) + stream << data(index, Qt::DisplayRole).toString(); + + EsmFile *file = item(index.row()); + stream << file->getMasters(); + } + } + + mimeData->setData("application/omwcontent", encodedData); + + return mimeData; +} + +bool EsxModel::DataFilesModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) +{ + if (action == Qt::IgnoreAction) + return true; + + if (action != Qt::MoveAction) + return false; + + if (!data->hasFormat("application/omwcontent")) + return false; + + int dropRow = row; + + if (dropRow == -1) + { + if (parent.isValid()) + dropRow = parent.row(); + else + dropRow = rowCount(QModelIndex()); + } + + if (parent.isValid()) + qDebug() << "parent: " << parent.data().toString(); + qDebug() << "dragged file: " << (qobject_cast(data))->fileName(); +// qDebug() << "inserting file: " << droppedfile->fileName() << " ahead of " << file->fileName(); + insertRows (dropRow, 1, QModelIndex()); + + + const EsmFile *draggedFile = qobject_cast(data); + + int dragRow = -1; + + for (int i = 0; i < mFiles.size(); ++i) + if (draggedFile->fileName() == mFiles.at(i)->fileName()) + { + dragRow = i; + break; + } + + for (int i = 0; i < mFiles.count(); ++i) + { + qDebug() << "index: " << i << "file: " << item(i)->fileName(); + qDebug() << mFiles.at(i)->fileName(); + } + + qDebug() << "drop row: " << dropRow << "; drag row: " << dragRow; +// const EsmFile *file = qobject_cast(data); + // int index = mFiles.indexOf(file); + //qDebug() << "file name: " << file->fileName() << "; index: " << index; + mFiles.swap(dropRow, dragRow); + //setData(index(startRow, 0), varFile); + emit dataChanged(index(0,0), index(rowCount(),0)); + return true; +} + void EsxModel::DataFilesModel::setEncoding(const QString &encoding) { mEncoding = encoding; @@ -51,17 +385,6 @@ Qt::CheckState EsxModel::DataFilesModel::checkState(const QModelIndex &index) return mCheckStates[item(index.row())->fileName()]; } -int EsxModel::DataFilesModel::columnCount(const QModelIndex &parent) const -{ - return parent.isValid() ? 0 : 1; -} - -int EsxModel::DataFilesModel::rowCount(const QModelIndex &parent) const -{ - return parent.isValid() ? 0 : mFiles.count(); -} - - bool EsxModel::DataFilesModel::moveRow(int oldrow, int row, const QModelIndex &parent) { if (oldrow < 0 || row < 0 || oldrow == row) @@ -76,124 +399,6 @@ bool EsxModel::DataFilesModel::moveRow(int oldrow, int row, const QModelIndex &p return true; } -QVariant EsxModel::DataFilesModel::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) - return QVariant(); - - const EsmFile *file = item(index.row()); - - if (!file) - return QVariant(); - - const int column = index.column(); - - switch (role) { - case Qt::EditRole: - case Qt::DisplayRole: { - - switch (column) { - case 0: - return file->fileName(); - case 1: - return file->author(); - case 2: - return QString("%1 kB").arg(int((file->size() + 1023) / 1024)); - case 3: - //return file->modified().toString(Qt::TextDate); - return file->modified().toString(Qt::ISODate); - case 4: - return file->accessed().toString(Qt::TextDate); - case 5: - return file->version(); - case 6: - return file->path(); - case 7: - return file->masters().join(", "); - case 8: - return file->description(); - } - } - - case Qt::TextAlignmentRole: { - switch (column) { - case 0: - case 1: - return Qt::AlignLeft + Qt::AlignVCenter; - case 2: - case 3: - case 4: - case 5: - return Qt::AlignRight + Qt::AlignVCenter; - default: - return Qt::AlignLeft + Qt::AlignVCenter; - } - } - - case Qt::ToolTipRole: - { - if (column != 0) - return QVariant(); - - if (file->version() == 0.0f) - return QVariant(); // Data not set - - QString tooltip = - QString("Author: %1
\ - Version: %2
\ -
Description:
%3
\ -
Dependencies: %4
") - .arg(file->author()) - .arg(QString::number(file->version())) - .arg(file->description()) - .arg(file->masters().join(", ")); - - - return tooltip; - - } - - case Qt::UserRole: - { - if (file->masters().size() == 0) - return "game"; - else - return "addon"; - } - - default: - return QVariant(); - } - -} - -Qt::ItemFlags EsxModel::DataFilesModel::flags(const QModelIndex &index) const -{ - if (!index.isValid()) - return Qt::NoItemFlags; - - const EsmFile *file = item(index.row()); - - Qt::ItemFlags dragDropFlags = Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; - Qt::ItemFlags checkFlags = Qt::ItemIsUserCheckable | Qt::ItemIsSelectable; - - if (!file) - return Qt::NoItemFlags; - - if (canBeChecked(file)) - { - if (index.column() == 0) - return dragDropFlags | checkFlags | Qt::ItemIsEnabled; - else - return Qt::ItemIsDropEnabled | Qt::ItemIsEnabled | Qt::ItemIsSelectable; - } - - if (index.column() == 0) - return dragDropFlags | checkFlags; - - return Qt::ItemIsDropEnabled | Qt::ItemIsSelectable; -} - QVariant EsxModel::DataFilesModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role != Qt::DisplayRole) @@ -215,22 +420,6 @@ QVariant EsxModel::DataFilesModel::headerData(int section, Qt::Orientation orien return QVariant(); } -bool EsxModel::DataFilesModel::setData(const QModelIndex &index, const QVariant &value, int role) -{ - if (!index.isValid()) - return false; - - if (role == Qt::EditRole) - { - qDebug() << "replacing: " << mFiles.at(index.row())->fileName(); -// mFiles.replace(index.row(), value.value()); - qDebug() << "with: " << mFiles.at(index.row())->fileName(); - emit dataChanged(index, index); - return true; - } - - return false; -} //!!!!!!!!!!!!!!!!!!!!!!! bool lessThanEsmFile(const EsxModel::EsmFile *e1, const EsxModel::EsmFile *e2) { @@ -345,32 +534,6 @@ QModelIndex EsxModel::DataFilesModel::indexFromItem(const EsmFile *item) const return QModelIndex(); } -const EsxModel::EsmFile* EsxModel::DataFilesModel::findItem(const QString &name) -{ - EsmFileList::ConstIterator it; - EsmFileList::ConstIterator itEnd = mFiles.constEnd(); - - int i = 0; - for (it = mFiles.constBegin(); it != itEnd; ++it) { - const EsmFile *file = item(i); - ++i; - - if (name == file->fileName()) - return file; - } - - // Not found - return 0; -} - -const EsxModel::EsmFile* EsxModel::DataFilesModel::item(int row) const -{ - if (row >= 0 && row < mFiles.count()) - return mFiles.at(row); - - return 0; -} - EsxModel::EsmFileList EsxModel::DataFilesModel::checkedItems() { EsmFileList list; @@ -443,110 +606,3 @@ bool EsxModel::DataFilesModel::canBeChecked(const EsmFile *file) const } return true; } - -Qt::DropActions EsxModel::DataFilesModel::supportedDropActions() const -{ - return Qt::CopyAction | Qt::MoveAction; -} - -QStringList EsxModel::DataFilesModel::mimeTypes() const -{ - QStringList types; - types << "application/omwcontent"; - return types; -} - -QMimeData *EsxModel::DataFilesModel::mimeData(const QModelIndexList &indexes) const -{ -// if (indexes.at(0).isValid()) -// return new EsmFile(*item(indexes.at(0).row())); - - return 0; -} - -bool EsxModel::DataFilesModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) -{ - if (action == Qt::IgnoreAction) - return true; - - if (action != Qt::MoveAction) - return false; - - if (!data->hasFormat("application/omwcontent")) - return false; - - int dropRow = row; - - if (dropRow == -1) - { - if (parent.isValid()) - dropRow = parent.row(); - else - dropRow = rowCount(QModelIndex()); - } - - if (parent.isValid()) - qDebug() << "parent: " << parent.data().toString(); - qDebug() << "dragged file: " << (qobject_cast(data))->fileName(); -// qDebug() << "inserting file: " << droppedfile->fileName() << " ahead of " << file->fileName(); - insertRows (dropRow, 1, QModelIndex()); - - - const EsmFile *draggedFile = qobject_cast(data); - - int dragRow = -1; - - for (int i = 0; i < mFiles.size(); ++i) - if (draggedFile->fileName() == mFiles.at(i)->fileName()) - { - dragRow = i; - break; - } - - for (int i = 0; i < mFiles.count(); ++i) - { - qDebug() << "index: " << i << "file: " << item(i)->fileName(); - qDebug() << mFiles.at(i)->fileName(); - } - - qDebug() << "drop row: " << dropRow << "; drag row: " << dragRow; -// const EsmFile *file = qobject_cast(data); - // int index = mFiles.indexOf(file); - //qDebug() << "file name: " << file->fileName() << "; index: " << index; - mFiles.swap(dropRow, dragRow); - //setData(index(startRow, 0), varFile); - emit dataChanged(index(0,0), index(rowCount(),0)); - return true; -} - -bool EsxModel::DataFilesModel::insertRows(int row, int count, const QModelIndex &parent) -{ - qDebug() << "inserting row: " << row << " count: " << count; - beginInsertRows(QModelIndex(),row, row+count-1); - - EsmFile *file = new EsmFile(); - - for (int i = 0; i < count; ++i) - mFiles.insert(row + i, file); - - endInsertRows(); - return true; -} - -bool EsxModel::DataFilesModel::removeRows(int row, int count, const QModelIndex &parent) -{ - qDebug() << "removing row: " << row << " count: " << count; - beginRemoveRows(QModelIndex(), row, row+count-1); - - for (int i = 0; i < count; ++i) - { - mFiles.removeAt(i); - } - - endRemoveRows(); - qDebug() <<"remove success"; - - emit dataChanged(parent, index(rowCount()-1, 0, parent)); - return true; -} - diff --git a/components/esxselector/model/esmfile.cpp b/components/esxselector/model/esmfile.cpp index 95cf703121..0e7f18373a 100644 --- a/components/esxselector/model/esmfile.cpp +++ b/components/esxselector/model/esmfile.cpp @@ -1,7 +1,12 @@ #include "esmfile.hpp" +#include +#include + +int EsxModel::EsmFile::sPropertyCount = 7; + EsxModel::EsmFile::EsmFile(QString fileName, ModelItem *parent) - : ModelItem(parent), mFileName(fileName), mSize(0), mVersion(0.0f) + : ModelItem(parent), mFileName(fileName), mVersion(0.0f) {} /* EsxModel::EsmFile::EsmFile(const EsmFile &file) @@ -22,15 +27,9 @@ void EsxModel::EsmFile::setAuthor(const QString &author) mAuthor = author; } -void EsxModel::EsmFile::setSize(const int size) -{ - mSize = size; -} - -void EsxModel::EsmFile::setDates(const QDateTime &modified, const QDateTime &accessed) +void EsxModel::EsmFile::setDate(const QDateTime &modified) { mModified = modified; - mAccessed = accessed; } void EsxModel::EsmFile::setVersion(float version) @@ -52,3 +51,52 @@ void EsxModel::EsmFile::setDescription(const QString &description) { mDescription = description; } + +QByteArray EsxModel::EsmFile::encodedData() const +{ + QByteArray encodedData; + QDataStream stream(&encodedData, QIODevice::WriteOnly); + + stream << mFileName << mAuthor << QString::number(mVersion) + << mModified.toString() << mPath << mDescription + << mMasters; + + return encodedData; +} + +void EsxModel::EsmFile::setProperty (const EsmFileProperty prop, const QString &value) +{ + switch (prop) + { + case Property_FileName: + mFileName = value; + break; + + case Property_Author: + mAuthor = value; + break; + + case Property_Version: + mVersion = value.toFloat(); + break; + + case Property_DateModified: + mModified = QDateTime::fromString(value); + break; + + case Property_Path: + mPath = value; + break; + + case Property_Description: + mDescription = value; + break; + + case Property_Master: + mMasters << value; + break; + + default: + break; + } +} diff --git a/components/esxselector/model/esmfile.hpp b/components/esxselector/model/esmfile.hpp index 0cda018b3c..9a1ea8af1f 100644 --- a/components/esxselector/model/esmfile.hpp +++ b/components/esxselector/model/esmfile.hpp @@ -6,8 +6,21 @@ #include "modelitem.hpp" +class QMimeData; + namespace EsxModel { + enum EsmFileProperty + { + Property_FileName = 0, + Property_Author = 1, + Property_Version = 2, + Property_DateModified = 3, + Property_Path = 4, + Property_Description = 5, + Property_Master = 6 + }; + class EsmFile : public ModelItem { Q_OBJECT @@ -21,10 +34,12 @@ namespace EsxModel ~EsmFile() {} + void setProperty (const EsmFileProperty prop, const QString &value); + void setFileName(const QString &fileName); void setAuthor(const QString &author); void setSize(const int size); - void setDates(const QDateTime &modified, const QDateTime &accessed); + void setDate(const QDateTime &modified); void setVersion(const float version); void setPath(const QString &path); void setMasters(const QStringList &masters); @@ -32,22 +47,23 @@ namespace EsxModel inline QString fileName() const { return mFileName; } inline QString author() const { return mAuthor; } - inline int size() const { return mSize; } inline QDateTime modified() const { return mModified; } - inline QDateTime accessed() const { return mAccessed; } inline float version() const { return mVersion; } inline QString path() const { return mPath; } inline QStringList masters() const { return mMasters; } inline QString description() const { return mDescription; } - //inline ModelItem *parent() const { return ModelItem::parent(); this->} + inline bool isMaster() const { return (mMasters.size() == 0); } + QByteArray encodedData() const; + + public: + static int sPropertyCount; private: + QString mFileName; QString mAuthor; - int mSize; QDateTime mModified; - QDateTime mAccessed; float mVersion; QString mPath; QStringList mMasters; diff --git a/components/esxselector/model/masterproxymodel.cpp b/components/esxselector/model/masterproxymodel.cpp index 46d68ca513..df74d03569 100644 --- a/components/esxselector/model/masterproxymodel.cpp +++ b/components/esxselector/model/masterproxymodel.cpp @@ -10,35 +10,4 @@ EsxModel::MasterProxyModel::MasterProxyModel(QObject *parent, QAbstractTableMode if (model) setSourceModel (model); - //connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(slotSourceModelChanged(QModelIndex, QModelIndex))); -} -/* -QVariant EsxModel::MasterProxyModel::data(const QModelIndex &index, int role) const -{ - if (index.isValid()) - return QSortFilterProxyModel::data (index, role); - - return 0; -} -*/ -void EsxModel::MasterProxyModel::slotSourceModelChanged(QModelIndex topLeft, QModelIndex botRight) -{ - qDebug() << "source data changed.. updating master proxy"; - emit dataChanged(index(0,0), index(rowCount()-1,0)); - - int curRow = -1; -/* - for (int i = 0; i < rowCount() - 1; ++i) - { - if (index(i,0).data(Qt::CheckState) == Qt::Checked) - { - curRow = i; - break; - } - } - - reset(); -*/ - if (curRow != -1); - // index(curRow, 0).setDataQt::CheckState) } diff --git a/components/esxselector/model/pluginsproxymodel.cpp b/components/esxselector/model/pluginsproxymodel.cpp index 412367b649..c543672b09 100644 --- a/components/esxselector/model/pluginsproxymodel.cpp +++ b/components/esxselector/model/pluginsproxymodel.cpp @@ -12,29 +12,3 @@ EsxModel::PluginsProxyModel::PluginsProxyModel(QObject *parent, ContentModel *mo if (model) setSourceModel (model); } - -EsxModel::PluginsProxyModel::~PluginsProxyModel() -{ -} - -QVariant EsxModel::PluginsProxyModel::data(const QModelIndex &index, int role) const -{ - switch (role) - { - case Qt::CheckStateRole: - { - if (index.column() != 0) - return QVariant(); - - return static_cast(sourceModel())->checkState(mapToSource(index)); - } - } - return QSortFilterProxyModel::data(index, role); -} - -bool EsxModel::PluginsProxyModel::removeRows(int position, int rows, const QModelIndex &parent) -{ - bool success = QSortFilterProxyModel::removeRows(position, rows, parent); - - return success; -} diff --git a/components/esxselector/view/contentselector.cpp b/components/esxselector/view/contentselector.cpp index bc7cc2b8bb..1f5e36d65b 100644 --- a/components/esxselector/view/contentselector.cpp +++ b/components/esxselector/view/contentselector.cpp @@ -1,8 +1,6 @@ #include "contentselector.hpp" #include "../model/datafilesmodel.hpp" -#include "../model/masterproxymodel.hpp" -#include "../model/pluginsproxymodel.hpp" #include "../model/contentmodel.hpp" #include "../model/esmfile.hpp" @@ -16,55 +14,73 @@ EsxView::ContentSelector::ContentSelector(QWidget *parent) : QDialog(parent) { setupUi(this); - // buildModelsAndViews(); - buildDragDropModelView(); + + buildSourceModel(); + buildMasterView(); + buildPluginsView(); + buildProfilesView(); + + updateViews(); + } -void EsxView::ContentSelector::buildDragDropModelView() + +void EsxView::ContentSelector::buildSourceModel() { mContentModel = new EsxModel::ContentModel(); + connect(mContentModel, SIGNAL(layoutChanged()), this, SLOT(updateViews())); +} - //mContentModel->addFiles("/home/joel/Projects/OpenMW/Data_Files"); - mMasterProxyModel = new EsxModel::MasterProxyModel(this, mContentModel); - mPluginsProxyModel = new EsxModel::PluginsProxyModel(this, mContentModel); - - tableView->setModel (mPluginsProxyModel); +void EsxView::ContentSelector::buildMasterView() +{ + mMasterProxyModel = new QSortFilterProxyModel(this); + mMasterProxyModel->setFilterRegExp(QString("game")); + mMasterProxyModel->setFilterRole (Qt::UserRole); + mMasterProxyModel->setSourceModel (mContentModel); masterView->setPlaceholderText(QString("Select a game file...")); masterView->setModel(mMasterProxyModel); + + connect(masterView, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCurrentMasterIndexChanged(int))); + + masterView->setCurrentIndex(-1); + masterView->setCurrentIndex(0); +} + +void EsxView::ContentSelector::buildPluginsView() +{ + mPluginsProxyModel = new QSortFilterProxyModel(this); + mPluginsProxyModel->setFilterRegExp (QString("addon")); + mPluginsProxyModel->setFilterRole (Qt::UserRole); + mPluginsProxyModel->setDynamicSortFilter (true); + mPluginsProxyModel->setSourceModel (mContentModel); + + tableView->setModel (mPluginsProxyModel); pluginView->setModel(mPluginsProxyModel); - profilesComboBox->setPlaceholderText(QString("Select a profile...")); - - updateViews(); connect(pluginView, SIGNAL(clicked(const QModelIndex &)), this, SLOT(slotPluginTableItemClicked(const QModelIndex &))); - connect(masterView, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCurrentMasterIndexChanged(int))); - connect(profilesComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCurrentProfileIndexChanged(int))); - - - connect(mContentModel, SIGNAL(layoutChanged()), this, SLOT(updateViews())); connect(tableView, SIGNAL(clicked(const QModelIndex &)), this, SLOT(slotPluginTableItemClicked(const QModelIndex &))); } -void EsxView::ContentSelector::buildModelsAndViews() +void EsxView::ContentSelector::buildProfilesView() { - // Models - mDataFilesModel = new EsxModel::DataFilesModel (this); - - // mMasterProxyModel = new EsxModel::MasterProxyModel (this, mDataFilesModel); - // mPluginsProxyModel = new EsxModel::PluginsProxyModel (this, mDataFilesModel); - - masterView->setPlaceholderText(QString("Select a game file...")); - masterView->setModel(mMasterProxyModel); - pluginView->setModel(mPluginsProxyModel); profilesComboBox->setPlaceholderText(QString("Select a profile...")); - - updateViews(); - connect(mDataFilesModel, SIGNAL(layoutChanged()), this, SLOT(updateViews())); - connect(pluginView, SIGNAL(clicked(const QModelIndex &)), this, SLOT(slotPluginTableItemClicked(const QModelIndex &))); - connect(masterView, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCurrentMasterIndexChanged(int))); connect(profilesComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCurrentProfileIndexChanged(int))); } +void EsxView::ContentSelector::updateViews() +{ + // Ensure the columns are hidden because sort() re-enables them + pluginView->setColumnHidden(1, true); + pluginView->setColumnHidden(2, true); + pluginView->setColumnHidden(3, true); + pluginView->setColumnHidden(4, true); + pluginView->setColumnHidden(5, true); + pluginView->setColumnHidden(6, true); + pluginView->setColumnHidden(7, true); + pluginView->setColumnHidden(8, true); + pluginView->resizeColumnsToContents(); +} + void EsxView::ContentSelector::addFiles(const QString &path) { mContentModel->addFiles(path); @@ -78,24 +94,6 @@ void EsxView::ContentSelector::setEncoding(const QString &encoding) mContentModel->setEncoding(encoding); } -void EsxView::ContentSelector::setCheckState(QModelIndex index, QSortFilterProxyModel *model) -{ - if (!index.isValid()) - return; - - if (!model) - return; - - QModelIndex sourceIndex = model->mapToSource(index); - - if (sourceIndex.isValid()) - { - (mContentModel->checkState(sourceIndex) == Qt::Checked) - ? mContentModel->setCheckState(sourceIndex, Qt::Unchecked) - : mContentModel->setCheckState(sourceIndex, Qt::Checked); - } -} - QStringList EsxView::ContentSelector::checkedItemsPaths() { QStringList itemPaths; @@ -106,21 +104,6 @@ QStringList EsxView::ContentSelector::checkedItemsPaths() return itemPaths; } -void EsxView::ContentSelector::updateViews() -{ - // Ensure the columns are hidden because sort() re-enables them - pluginView->setColumnHidden(1, true); - pluginView->setColumnHidden(2, true); - pluginView->setColumnHidden(3, true); - pluginView->setColumnHidden(4, true); - pluginView->setColumnHidden(5, true); - pluginView->setColumnHidden(6, true); - pluginView->setColumnHidden(7, true); - pluginView->setColumnHidden(8, true); - pluginView->resizeColumnsToContents(); - -} - void EsxView::ContentSelector::slotCurrentProfileIndexChanged(int index) { emit profileChanged(index); @@ -128,17 +111,40 @@ void EsxView::ContentSelector::slotCurrentProfileIndexChanged(int index) void EsxView::ContentSelector::slotCurrentMasterIndexChanged(int index) { - QObject *object = QObject::sender(); + static int oldIndex = -1; - // Not a signal-slot call - if (!object) - return; + QAbstractItemModel *const model = masterView->model(); + QSortFilterProxyModel *proxy = dynamic_cast(model); - setCheckState(mMasterProxyModel->index(index, 0), mMasterProxyModel); + if (proxy) + proxy->setDynamicSortFilter(false); + + if (oldIndex > -1) + model->setData(model->index(oldIndex, 0), false, Qt::UserRole + 1); + + oldIndex = index; + + model->setData(model->index(index, 0), true, Qt::UserRole + 1); + + if (proxy) + proxy->setDynamicSortFilter(true); } void EsxView::ContentSelector::slotPluginTableItemClicked(const QModelIndex &index) { qDebug() << "setting checkstate in plugin..."; - setCheckState(index, mPluginsProxyModel); + + QAbstractItemModel *const model = pluginView->model(); + QSortFilterProxyModel *proxy = dynamic_cast(model); + + if (proxy) + proxy->setDynamicSortFilter(false); + + if (model->data(index, Qt::CheckStateRole).toInt() == Qt::Unchecked) + model->setData(index, Qt::Checked, Qt::CheckStateRole); + else + model->setData(index, Qt::Unchecked, Qt::CheckStateRole); + + if (proxy) + proxy->setDynamicSortFilter(true); } diff --git a/components/esxselector/view/contentselector.hpp b/components/esxselector/view/contentselector.hpp index 06cc8f3f0b..e074fe6880 100644 --- a/components/esxselector/view/contentselector.hpp +++ b/components/esxselector/view/contentselector.hpp @@ -9,8 +9,6 @@ namespace EsxModel { class ContentModel; class DataFilesModel; - class PluginsProxyModel; - class MasterProxyModel; } class QSortFilterProxyModel; @@ -25,8 +23,8 @@ namespace EsxView EsxModel::DataFilesModel *mDataFilesModel; EsxModel::ContentModel *mContentModel; - EsxModel::MasterProxyModel *mMasterProxyModel; - EsxModel::PluginsProxyModel *mPluginsProxyModel; + QSortFilterProxyModel *mMasterProxyModel; + QSortFilterProxyModel *mPluginsProxyModel; public: explicit ContentSelector(QWidget *parent = 0); @@ -39,7 +37,12 @@ namespace EsxView void setCheckState(QModelIndex index, QSortFilterProxyModel *model); QStringList checkedItemsPaths(); void on_checkAction_triggered(); - void buildDragDropModelView(); + + private: + void buildSourceModel(); + void buildMasterView(); + void buildPluginsView(); + void buildProfilesView(); signals: void profileChanged(int index); From 244e5819529e2be6e4c0a790442d228fc4a133fc Mon Sep 17 00:00:00 2001 From: graffy76 Date: Thu, 19 Sep 2013 06:53:09 -0500 Subject: [PATCH 043/434] Finished implementing drag / drop refactored datafilesmodel (now contentmodel) refactored esmfil --- components/esxselector/model/contentmodel.cpp | 302 ++++++++---------- components/esxselector/model/contentmodel.hpp | 19 +- components/esxselector/model/esmfile.cpp | 67 +++- components/esxselector/model/esmfile.hpp | 50 +-- .../esxselector/view/contentselector.cpp | 6 +- files/ui/datafilespage.ui | 39 +-- 6 files changed, 224 insertions(+), 259 deletions(-) diff --git a/components/esxselector/model/contentmodel.cpp b/components/esxselector/model/contentmodel.cpp index bfbb1bef7a..d23ea78014 100644 --- a/components/esxselector/model/contentmodel.cpp +++ b/components/esxselector/model/contentmodel.cpp @@ -1,17 +1,37 @@ #include "contentmodel.hpp" #include "esmfile.hpp" -#include + #include #include #include +#include EsxModel::ContentModel::ContentModel(QObject *parent) : - QAbstractTableModel(parent), mEncoding("win1252") -{} + QAbstractTableModel(parent), + mMimeType ("application/omwcontent"), + mMimeTypes (QStringList() << mMimeType), + mColumnCount (1), + mDragDropFlags (Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled), + mDefaultFlags (Qt::ItemIsDropEnabled | Qt::ItemIsSelectable), + mDropActions (Qt::CopyAction | Qt::MoveAction) +{ + setEncoding ("win1252"); + uncheckAll(); +} void EsxModel::ContentModel::setEncoding(const QString &encoding) { - mEncoding = encoding; + if (encoding == QLatin1String("win1252")) + mCodec = QTextCodec::codecForName("windows-1252"); + + else if (encoding == QLatin1String("win1251")) + mCodec = QTextCodec::codecForName("windows-1251"); + + else if (encoding == QLatin1String("win1250")) + mCodec = QTextCodec::codecForName("windows-1250"); + + else + return; // This should never happen; } int EsxModel::ContentModel::columnCount(const QModelIndex &parent) const @@ -19,7 +39,7 @@ int EsxModel::ContentModel::columnCount(const QModelIndex &parent) const if (parent.isValid()) return 0; - return 1; + return mColumnCount; } int EsxModel::ContentModel::rowCount(const QModelIndex &parent) const @@ -30,22 +50,28 @@ int EsxModel::ContentModel::rowCount(const QModelIndex &parent) const return mFiles.size(); } -EsxModel::EsmFile* EsxModel::ContentModel::item(int row) const +const EsxModel::EsmFile* EsxModel::ContentModel::item(int row) const +{ + if (row >= 0 && row < mFiles.size()) + return mFiles.at(row); + + return 0; +} + +EsxModel::EsmFile *EsxModel::ContentModel::item(int row) { if (row >= 0 && row < mFiles.count()) return mFiles.at(row); return 0; } - -EsxModel::EsmFile* EsxModel::ContentModel::findItem(const QString &name) +const EsxModel::EsmFile *EsxModel::ContentModel::findItem(const QString &name) const { - for (int i = 0; i < mFiles.size(); ++i) + foreach (const EsmFile *file, mFiles) { - if (name == item(i)->fileName()) - return item(i); + if (name == file->fileName()) + return file; } - return 0; } @@ -62,19 +88,15 @@ Qt::ItemFlags EsxModel::ContentModel::flags(const QModelIndex &index) const if (!index.isValid()) return Qt::NoItemFlags; - EsmFile *file = item(index.row()); + const EsmFile *file = item(index.row()); if (!file) return Qt::NoItemFlags; - Qt::ItemFlags dragDropFlags = Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; - Qt::ItemFlags checkFlags = Qt::ItemIsUserCheckable; - Qt::ItemFlags defaultFlags = Qt::ItemIsDropEnabled | Qt::ItemIsSelectable; - if (canBeChecked(file)) - return Qt::ItemIsEnabled | dragDropFlags | checkFlags | defaultFlags; - else - return defaultFlags; + return Qt::ItemIsEnabled | mDragDropFlags | mDefaultFlags; + + return mDefaultFlags; } QVariant EsxModel::ContentModel::data(const QModelIndex &index, int role) const @@ -85,7 +107,7 @@ QVariant EsxModel::ContentModel::data(const QModelIndex &index, int role) const if (index.row() >= mFiles.size()) return QVariant(); - EsmFile *file = item(index.row()); + const EsmFile *file = item(index.row()); if (!file) return QVariant(); @@ -97,25 +119,11 @@ QVariant EsxModel::ContentModel::data(const QModelIndex &index, int role) const case Qt::EditRole: case Qt::DisplayRole: { - switch (column) - { - case 0: - return file->fileName(); - case 1: - return file->author(); - case 2: - return file->modified().toString(Qt::ISODate); - case 3: - return file->version(); - case 4: - return file->path(); - case 5: - return file->masters().join(", "); - case 6: - return file->description(); - } + if (column >=0 && column <=EsmFile::FileProperty_Master) + return file->fileProperty(static_cast(column)); return QVariant(); + break; } case Qt::TextAlignmentRole: @@ -132,6 +140,7 @@ QVariant EsxModel::ContentModel::data(const QModelIndex &index, int role) const return Qt::AlignLeft + Qt::AlignVCenter; } return QVariant(); + break; } case Qt::ToolTipRole: @@ -139,23 +148,16 @@ QVariant EsxModel::ContentModel::data(const QModelIndex &index, int role) const if (column != 0) return QVariant(); - if (file->version() == 0.0f) - return QVariant(); // Data not set - - return QString("Author: %1
\ - Version: %2
\ -
Description:
%3
\ -
Dependencies: %4
") - .arg(file->author()) - .arg(QString::number(file->version())) - .arg(file->description()) - .arg(file->masters().join(", ")); + return file->toolTip(); + break; } case Qt::CheckStateRole: + { if (!file->isMaster()) return isChecked(file->fileName()); break; + } case Qt::UserRole: { @@ -179,64 +181,63 @@ bool EsxModel::ContentModel::setData(const QModelIndex &index, const QVariant &v EsmFile *file = item(index.row()); QString fileName = file->fileName(); + bool success = false; switch(role) { case Qt::EditRole: - { - QStringList list = value.toStringList(); + { + QStringList list = value.toStringList(); - //iterate the string list, assigning values to proeprties - //index-enum correspondence 1:1 - for (int i = 0; i < EsxModel::Property_Master; i++) - file->setProperty(static_cast(i), list.at(i)); + for (int i = 0; i < EsmFile::FileProperty_Master; i++) + file->setFileProperty(static_cast(i), list.at(i)); - //iterate the remainder of the string list, assifning everything - // as - for (int i = EsxModel::Property_Master; i < list.size(); i++) - file->setProperty (EsxModel::Property_Master, list.at(i)); + for (int i = EsmFile::FileProperty_Master; i < list.size(); i++) + file->setFileProperty (EsmFile::FileProperty_Master, list.at(i)); - //emit data changed for the item itself - emit dataChanged(index, index); + emit dataChanged(index, index); - return true; - } - break; + success = true; + } + break; case Qt::UserRole+1: + { + setCheckState(fileName, value.toBool()); + + emit dataChanged(index, index); + + foreach (EsmFile *file, mFiles) { - setCheckState(fileName, value.toBool()); - - emit dataChanged(index, index); - - for(int i = 0; i < mFiles.size(); i++) + if (file->masters().contains(fileName)) { - - if (mFiles.at(i)->masters().contains(fileName)) - { - QModelIndex idx = QAbstractTableModel::index(i, 0); - emit dataChanged(idx, idx); - } + QModelIndex idx = indexFromItem(file); + emit dataChanged(idx, idx); } - - return true; } - break; + success = true; + } + break; case Qt::CheckStateRole: - { - bool checked = ((value.toInt() == Qt::Checked) && !isChecked(fileName)); + { + int checkValue = value.toInt(); - setCheckState(fileName, checked); + if ((checkValue==Qt::Checked) && !isChecked(fileName)) + setCheckState(fileName, true); + else if ((checkValue == Qt::Checked) && isChecked (fileName)) + setCheckState(fileName, false); + else if (checkValue == Qt::Unchecked) + setCheckState(fileName, false); - emit dataChanged(index, index); + emit dataChanged(index, index); - return true; - } - break; + success = true; + } + break; } - return false; + return success; } bool EsxModel::ContentModel::insertRows(int position, int rows, const QModelIndex &parent) @@ -271,16 +272,12 @@ bool EsxModel::ContentModel::removeRows(int position, int rows, const QModelInde Qt::DropActions EsxModel::ContentModel::supportedDropActions() const { - return Qt::CopyAction | Qt::MoveAction; + return mDropActions; } QStringList EsxModel::ContentModel::mimeTypes() const { - QStringList types; - - types << "application/omwcontent"; - - return types; + return mMimeTypes; } QMimeData *EsxModel::ContentModel::mimeData(const QModelIndexList &indexes) const @@ -292,14 +289,11 @@ QMimeData *EsxModel::ContentModel::mimeData(const QModelIndexList &indexes) cons if (!index.isValid()) continue; - QByteArray fileData = item(index.row())->encodedData(); - - foreach (const char c, fileData) - encodedData.append(c); + encodedData.append(item(index.row())->encodedData()); } QMimeData *mimeData = new QMimeData(); - mimeData->setData("application/omwcontent", encodedData); + mimeData->setData(mMimeType, encodedData); return mimeData; } @@ -309,13 +303,13 @@ bool EsxModel::ContentModel::dropMimeData(const QMimeData *data, Qt::DropAction if (action == Qt::IgnoreAction) return true; - if (!data->hasFormat("application/omwcontent")) - return false; - if (column > 0) return false; - int beginRow; + if (!data->hasFormat(mMimeType)) + return false; + + int beginRow = rowCount(); if (row != -1) beginRow = row; @@ -323,23 +317,28 @@ bool EsxModel::ContentModel::dropMimeData(const QMimeData *data, Qt::DropAction else if (parent.isValid()) beginRow = parent.row(); - else - beginRow = rowCount(); - - QByteArray encodedData = data->data("application/omwcontent"); + QByteArray encodedData = data->data(mMimeType); QDataStream stream(&encodedData, QIODevice::ReadOnly); while (!stream.atEnd()) { - QStringList values; - for (int i = 0; i < EsmFile::sPropertyCount; ++i) - stream >> values; + QString value; + QStringList values; + QStringList masters; + + for (int i = 0; i < EsmFile::FileProperty_Master; ++i) + { + stream >> value; + values << value; + } + + stream >> masters; insertRows(beginRow, 1); QModelIndex idx = index(beginRow++, 0, QModelIndex()); - setData(idx, values, Qt::EditRole); + setData(idx, QStringList() << values << masters, Qt::EditRole); } return true; @@ -349,37 +348,8 @@ bool EsxModel::ContentModel::canBeChecked(const EsmFile *file) const { //element can be checked if all its dependencies are foreach (const QString &master, file->masters()) - { - {// if the master is not found in checkstates - // or it is not specifically checked, return false - if (!mCheckStates.contains(master)) - return false; - - if (!isChecked(master)) - return false; - } - - bool found = false; - - //iterate each file, if it is not a master and - //does not have a master that is currently checked, - //return false. - foreach(const EsmFile *file, mFiles) - { - QString filename = file->fileName(); - - found = (filename == master); - - if (found) - { - if (!isChecked(filename)) - return false; - } - } - - if (!found) + if (!isChecked(master)) return false; - } return true; } @@ -387,9 +357,8 @@ bool EsxModel::ContentModel::canBeChecked(const EsmFile *file) const void EsxModel::ContentModel::addFile(EsmFile *file) { beginInsertRows(QModelIndex(), mFiles.count(), mFiles.count()); - { mFiles.append(file); - } endInsertRows(); + endInsertRows(); } void EsxModel::ContentModel::addFiles(const QString &path) @@ -400,45 +369,26 @@ void EsxModel::ContentModel::addFiles(const QString &path) dir.setNameFilters(filters); // Create a decoder for non-latin characters in esx metadata - QTextCodec *codec; + QTextDecoder *decoder = mCodec->makeDecoder(); - if (mEncoding == QLatin1String("win1252")) { - codec = QTextCodec::codecForName("windows-1252"); - } else if (mEncoding == QLatin1String("win1251")) { - codec = QTextCodec::codecForName("windows-1251"); - } else if (mEncoding == QLatin1String("win1250")) { - codec = QTextCodec::codecForName("windows-1250"); - } else { - return; // This should never happen; - } - - QTextDecoder *decoder = codec->makeDecoder(); - - foreach (const QString &path, dir.entryList()) { + foreach (const QString &path, dir.entryList()) + { QFileInfo info(dir.absoluteFilePath(path)); EsmFile *file = new EsmFile(path); try { ESM::ESMReader fileReader; - ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(mEncoding.toStdString())); + ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(QString(mCodec->name()).toStdString())); fileReader.setEncoder(&encoder); fileReader.open(dir.absoluteFilePath(path).toStdString()); - std::vector mlist = fileReader.getMasters(); + foreach (const ESM::Header::MasterData &item, fileReader.getMasters()) + file->addMaster(QString::fromStdString(item.name)); - QStringList masters; - - for (unsigned int i = 0; i < mlist.size(); ++i) { - QString master = QString::fromStdString(mlist[i].name); - masters.append(master); - } - - file->setAuthor(decoder->toUnicode(fileReader.getAuthor().c_str())); - //file->setSize(info.size()); - file->setDate(info.lastModified()); - file->setVersion(0.0f); - file->setPath(info.absoluteFilePath()); - file->setMasters(masters); + file->setAuthor (decoder->toUnicode(fileReader.getAuthor().c_str())); + file->setDate (info.lastModified()); + file->setVersion (fileReader.getFVer()); + file->setPath (info.absoluteFilePath()); file->setDescription(decoder->toUnicode(fileReader.getDesc().c_str())); @@ -459,7 +409,10 @@ void EsxModel::ContentModel::addFiles(const QString &path) bool EsxModel::ContentModel::isChecked(const QString& name) const { - return (mCheckStates[name] == Qt::Checked); + if (mCheckStates.contains(name)) + return (mCheckStates[name] == Qt::Checked); + + return false; } void EsxModel::ContentModel::setCheckState(const QString &name, bool isChecked) @@ -479,12 +432,9 @@ EsxModel::ContentFileList EsxModel::ContentModel::checkedItems() const { ContentFileList list; - for (int i = 0; i < mFiles.size(); ++i) + foreach (EsmFile *file, mFiles) { - EsmFile *file = item(i); - - // Only add the items that are in the checked list and available - if (mCheckStates[file->fileName()] == Qt::Checked && canBeChecked(file)) + if (isChecked(file->fileName())) list << file; } diff --git a/components/esxselector/model/contentmodel.hpp b/components/esxselector/model/contentmodel.hpp index 61c823ab38..6a2dd88ca5 100644 --- a/components/esxselector/model/contentmodel.hpp +++ b/components/esxselector/model/contentmodel.hpp @@ -2,7 +2,7 @@ #define CONTENTMODEL_HPP #include - +#include namespace EsxModel { class EsmFile; @@ -35,7 +35,7 @@ namespace EsxModel void addFiles(const QString &path); QModelIndex indexFromItem(EsmFile *item) const; - EsxModel::EsmFile *findItem(const QString &name); + const EsxModel::EsmFile *findItem(const QString &name) const; bool isChecked(const QString &name) const; void setCheckState(const QString &name, bool isChecked); @@ -45,16 +45,21 @@ namespace EsxModel private: void addFile(EsmFile *file); - EsmFile* item(int row) const; + const EsmFile *item(int row) const; + EsmFile *item(int row); bool canBeChecked(const EsmFile *file) const; ContentFileList mFiles; QHash mCheckStates; - QString mEncoding; + QTextCodec *mCodec; - signals: - - public slots: + public: + QString mMimeType; + QStringList mMimeTypes; + int mColumnCount; + Qt::ItemFlags mDragDropFlags; + Qt::ItemFlags mDefaultFlags; + Qt::DropActions mDropActions; }; } diff --git a/components/esxselector/model/esmfile.cpp b/components/esxselector/model/esmfile.cpp index 0e7f18373a..bd76dc4a00 100644 --- a/components/esxselector/model/esmfile.cpp +++ b/components/esxselector/model/esmfile.cpp @@ -4,19 +4,15 @@ #include int EsxModel::EsmFile::sPropertyCount = 7; +QString EsxModel::EsmFile::sToolTip = QString("Author: %1
\ + Version: %2
\ +
Description:
%3
\ +
Dependencies: %4
"); + EsxModel::EsmFile::EsmFile(QString fileName, ModelItem *parent) : ModelItem(parent), mFileName(fileName), mVersion(0.0f) {} -/* -EsxModel::EsmFile::EsmFile(const EsmFile &file) - : ModelItem(file.parent()), mFileName(file.mFileName), mSize(file.mSize), - mVersion(file.mVersion), mAuthor(file.mAuthor), mModified(file.mModified), - mAccessed(file.mAccessed), mPath(file.mPath), mMasters(file.mMasters), - mDescription(file.mDescription) -{} - -*/ void EsxModel::EsmFile::setFileName(const QString &fileName) { mFileName = fileName; @@ -64,35 +60,72 @@ QByteArray EsxModel::EsmFile::encodedData() const return encodedData; } -void EsxModel::EsmFile::setProperty (const EsmFileProperty prop, const QString &value) +QVariant EsxModel::EsmFile::fileProperty(const FileProperty prop) const { switch (prop) { - case Property_FileName: + case FileProperty_FileName: + return mFileName; + break; + + case FileProperty_Author: + return mAuthor; + break; + + case FileProperty_Version: + return mVersion; + break; + + case FileProperty_DateModified: + return mModified.toString(Qt::ISODate); + break; + + case FileProperty_Path: + return mPath; + break; + + case FileProperty_Description: + return mDescription; + break; + + case FileProperty_Master: + return mMasters; + break; + + default: + break; + } + return QVariant(); +} +void EsxModel::EsmFile::setFileProperty (const FileProperty prop, const QString &value) +{ + switch (prop) + { + case FileProperty_FileName: mFileName = value; break; - case Property_Author: + case FileProperty_Author: mAuthor = value; break; - case Property_Version: + case FileProperty_Version: mVersion = value.toFloat(); break; - case Property_DateModified: + case FileProperty_DateModified: mModified = QDateTime::fromString(value); break; - case Property_Path: + case FileProperty_Path: mPath = value; break; - case Property_Description: + case FileProperty_Description: mDescription = value; break; - case Property_Master: + case FileProperty_Master: mMasters << value; break; diff --git a/components/esxselector/model/esmfile.hpp b/components/esxselector/model/esmfile.hpp index 9a1ea8af1f..4f6d7a6244 100644 --- a/components/esxselector/model/esmfile.hpp +++ b/components/esxselector/model/esmfile.hpp @@ -10,17 +10,6 @@ class QMimeData; namespace EsxModel { - enum EsmFileProperty - { - Property_FileName = 0, - Property_Author = 1, - Property_Version = 2, - Property_DateModified = 3, - Property_Path = 4, - Property_Description = 5, - Property_Master = 6 - }; - class EsmFile : public ModelItem { Q_OBJECT @@ -28,13 +17,24 @@ namespace EsxModel public: + enum FileProperty + { + FileProperty_FileName = 0, + FileProperty_Author = 1, + FileProperty_Version = 2, + FileProperty_DateModified = 3, + FileProperty_Path = 4, + FileProperty_Description = 5, + FileProperty_Master = 6 + }; + EsmFile(QString fileName = QString(), ModelItem *parent = 0); // EsmFile(const EsmFile &); ~EsmFile() {} - void setProperty (const EsmFileProperty prop, const QString &value); + void setFileProperty (const FileProperty prop, const QString &value); void setFileName(const QString &fileName); void setAuthor(const QString &author); @@ -45,19 +45,28 @@ namespace EsxModel void setMasters(const QStringList &masters); void setDescription(const QString &description); - inline QString fileName() const { return mFileName; } - inline QString author() const { return mAuthor; } - inline QDateTime modified() const { return mModified; } - inline float version() const { return mVersion; } - inline QString path() const { return mPath; } - inline QStringList masters() const { return mMasters; } - inline QString description() const { return mDescription; } + inline void addMaster (const QString &name) {mMasters.append(name); } + QVariant fileProperty (const FileProperty prop) const; - inline bool isMaster() const { return (mMasters.size() == 0); } + inline QString fileName() const { return mFileName; } + inline QString author() const { return mAuthor; } + inline QDateTime modified() const { return mModified; } + inline float version() const { return mVersion; } + inline QString path() const { return mPath; } + inline const QStringList &masters() const { return mMasters; } + inline QString description() const { return mDescription; } + inline QString toolTip() const { return sToolTip.arg(mAuthor) + .arg(mVersion) + .arg(mDescription) + .arg(mMasters.join(", ")); + } + + inline bool isMaster() const { return (mMasters.size() == 0); } QByteArray encodedData() const; public: static int sPropertyCount; + static QString sToolTip; private: @@ -68,6 +77,7 @@ namespace EsxModel QString mPath; QStringList mMasters; QString mDescription; + QString mToolTip; }; } diff --git a/components/esxselector/view/contentselector.cpp b/components/esxselector/view/contentselector.cpp index 1f5e36d65b..4f8ac7253a 100644 --- a/components/esxselector/view/contentselector.cpp +++ b/components/esxselector/view/contentselector.cpp @@ -54,11 +54,9 @@ void EsxView::ContentSelector::buildPluginsView() mPluginsProxyModel->setDynamicSortFilter (true); mPluginsProxyModel->setSourceModel (mContentModel); - tableView->setModel (mPluginsProxyModel); pluginView->setModel(mPluginsProxyModel); connect(pluginView, SIGNAL(clicked(const QModelIndex &)), this, SLOT(slotPluginTableItemClicked(const QModelIndex &))); - connect(tableView, SIGNAL(clicked(const QModelIndex &)), this, SLOT(slotPluginTableItemClicked(const QModelIndex &))); } void EsxView::ContentSelector::buildProfilesView() @@ -120,10 +118,12 @@ void EsxView::ContentSelector::slotCurrentMasterIndexChanged(int index) proxy->setDynamicSortFilter(false); if (oldIndex > -1) + qDebug() << "clearing old master check state"; model->setData(model->index(oldIndex, 0), false, Qt::UserRole + 1); oldIndex = index; + qDebug() << "setting new master check state"; model->setData(model->index(index, 0), true, Qt::UserRole + 1); if (proxy) @@ -132,8 +132,6 @@ void EsxView::ContentSelector::slotCurrentMasterIndexChanged(int index) void EsxView::ContentSelector::slotPluginTableItemClicked(const QModelIndex &index) { - qDebug() << "setting checkstate in plugin..."; - QAbstractItemModel *const model = pluginView->model(); QSortFilterProxyModel *proxy = dynamic_cast(model); diff --git a/files/ui/datafilespage.ui b/files/ui/datafilespage.ui index 76689627b0..5b0a6d2292 100644 --- a/files/ui/datafilespage.ui +++ b/files/ui/datafilespage.ui @@ -7,7 +7,7 @@ 0 0 518 - 310 + 313 @@ -44,6 +44,9 @@ Qt::DefaultContextMenu + + true + QAbstractItemView::NoEditTriggers @@ -85,40 +88,6 @@ - - - - QAbstractItemView::NoEditTriggers - - - true - - - false - - - QAbstractItemView::DragDrop - - - Qt::MoveAction - - - QAbstractItemView::SingleSelection - - - QAbstractItemView::SelectRows - - - false - - - false - - - false - - - From cfdc19c4272804b9ed682704e8fa30d31a85a062 Mon Sep 17 00:00:00 2001 From: graffy76 Date: Sat, 21 Sep 2013 23:06:29 -0500 Subject: [PATCH 044/434] Renamed esxSelector to contentSelector Fixed datafilespage model implementation in launcher Filtered addons in table view by selected game file --- apps/esmtool/esmtool.cpp | 4 +- apps/launcher/datafilespage.cpp | 195 ++++-- apps/launcher/datafilespage.hpp | 32 +- apps/launcher/graphicspage.cpp | 2 +- apps/launcher/utils/textinputdialog.cpp | 4 +- apps/launcher/utils/textinputdialog.hpp | 7 +- apps/opencs/editor.cpp | 4 +- apps/opencs/main.cpp | 2 +- apps/opencs/view/doc/filedialog.cpp | 12 +- apps/opencs/view/doc/filedialog.hpp | 6 +- apps/openmw/mwworld/esmstore.cpp | 2 +- components/CMakeLists.txt | 7 +- .../model/contentmodel.cpp | 137 ++-- .../model/contentmodel.hpp | 15 +- .../model/esmfile.cpp | 53 +- .../model/esmfile.hpp | 42 +- .../contentselector/model/modelitem.cpp | 69 ++ .../model/modelitem.hpp | 2 +- .../model/naturalsort.cpp | 0 .../model/naturalsort.hpp | 0 .../view/comboboxlineedit.cpp | 6 +- .../view/comboboxlineedit.hpp | 2 +- .../contentselector/view/contentselector.cpp | 134 ++++ .../view/contentselector.hpp | 32 +- .../view/lineedit.cpp | 6 +- .../view/lineedit.hpp | 2 +- .../view/profilescombobox.cpp | 14 +- .../view/profilescombobox.hpp | 2 +- components/esm/esmreader.hpp | 2 +- components/esm/loadcell.cpp | 4 +- .../esxselector/model/datafilesmodel.cpp | 608 ------------------ .../esxselector/model/datafilesmodel.hpp | 80 --- .../esxselector/model/masterproxymodel.cpp | 13 - .../esxselector/model/masterproxymodel.hpp | 25 - components/esxselector/model/modelitem.cpp | 69 -- .../esxselector/model/pluginsproxymodel.cpp | 14 - .../esxselector/model/pluginsproxymodel.hpp | 28 - components/esxselector/model/sourcemodel.cpp | 6 - components/esxselector/model/sourcemodel.h | 18 - .../esxselector/view/contentselector.cpp | 148 ----- files/ui/datafilespage.ui | 12 +- 41 files changed, 585 insertions(+), 1235 deletions(-) rename components/{esxselector => contentselector}/model/contentmodel.cpp (64%) rename components/{esxselector => contentselector}/model/contentmodel.hpp (86%) rename components/{esxselector => contentselector}/model/esmfile.cpp (54%) rename components/{esxselector => contentselector}/model/esmfile.hpp (58%) create mode 100644 components/contentselector/model/modelitem.cpp rename components/{esxselector => contentselector}/model/modelitem.hpp (96%) rename components/{esxselector => contentselector}/model/naturalsort.cpp (100%) rename components/{esxselector => contentselector}/model/naturalsort.hpp (100%) rename components/{esxselector => contentselector}/view/comboboxlineedit.cpp (83%) rename components/{esxselector => contentselector}/view/comboboxlineedit.hpp (96%) create mode 100644 components/contentselector/view/contentselector.cpp rename components/{esxselector => contentselector}/view/contentselector.hpp (51%) rename components/{esxselector => contentselector}/view/lineedit.cpp (87%) rename components/{esxselector => contentselector}/view/lineedit.hpp (96%) rename components/{esxselector => contentselector}/view/profilescombobox.cpp (83%) rename components/{esxselector => contentselector}/view/profilescombobox.hpp (96%) delete mode 100644 components/esxselector/model/datafilesmodel.cpp delete mode 100644 components/esxselector/model/datafilesmodel.hpp delete mode 100644 components/esxselector/model/masterproxymodel.cpp delete mode 100644 components/esxselector/model/masterproxymodel.hpp delete mode 100644 components/esxselector/model/modelitem.cpp delete mode 100644 components/esxselector/model/pluginsproxymodel.cpp delete mode 100644 components/esxselector/model/pluginsproxymodel.hpp delete mode 100644 components/esxselector/model/sourcemodel.cpp delete mode 100644 components/esxselector/model/sourcemodel.h delete mode 100644 components/esxselector/view/contentselector.cpp diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index a60e9f0e20..6ccf9c3f3b 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -305,14 +305,14 @@ int load(Arguments& info) info.data.author = esm.getAuthor(); info.data.description = esm.getDesc(); - info.data.masters = esm.getMasters(); + info.data.masters = esm.getGameFiles(); if (!quiet) { std::cout << "Author: " << esm.getAuthor() << std::endl << "Description: " << esm.getDesc() << std::endl << "File format version: " << esm.getFVer() << std::endl; - std::vector m = esm.getMasters(); + std::vector m = esm.getGameFiles(); if (!m.empty()) { std::cout << "Masters:" << std::endl; diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 7069737eff..1526b705ae 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -4,22 +4,22 @@ #include #include #include +#include #include -#include -#include +#include -#include -#include -#include +#include +#include +#include -#include "components/esxselector/model/masterproxymodel.hpp" #include "settings/gamesettings.hpp" #include "settings/launchersettings.hpp" #include "utils/textinputdialog.hpp" -#include "components/esxselector/view/contentselector.hpp" +#include "components/contentselector/view/contentselector.hpp" +#include "components/contentselector/model/contentmodel.hpp" #include @@ -28,8 +28,10 @@ DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gam , mGameSettings(gameSettings) , mLauncherSettings(launcherSettings) { - mContentSelector.setParent(parent); - QMetaObject::connectSlotsByName(this); + setupUi(this); + // mContentSelector.setParent(parent); + + // QMetaObject::connectSlotsByName(this); projectGroupBox->hide(); @@ -38,8 +40,82 @@ DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gam connect(mNewProfileDialog->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(updateOkButton(QString))); + + + buildContentModel(); + buildGameFileView(); + buildAddonView(); + buildProfilesView(); + + createActions(); setupDataFiles(); + + + updateViews(); +} + +void DataFilesPage::buildContentModel() +{ + mContentModel = new ContentSelectorModel::ContentModel(); + connect(mContentModel, SIGNAL(layoutChanged()), this, SLOT(updateViews())); +} + +void DataFilesPage::buildGameFileView() +{ + mGameFileProxyModel = new QSortFilterProxyModel(this); + mGameFileProxyModel->setFilterRegExp(QString::number((int)ContentSelectorModel::ContentType_GameFile)); + mGameFileProxyModel->setFilterRole (Qt::UserRole); + mGameFileProxyModel->setSourceModel (mContentModel); + + gameFileView->setPlaceholderText(QString("Select a game file...")); + gameFileView->setModel(mGameFileProxyModel); + + connect(gameFileView, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCurrentGameFileIndexChanged(int))); + + gameFileView->setCurrentIndex(-1); + gameFileView->setCurrentIndex(0); +} + +void DataFilesPage::buildAddonView() +{ + mAddonProxyModel = new QSortFilterProxyModel(this); + mAddonProxyModel->setFilterRegExp (QString::number((int)ContentSelectorModel::ContentType_Addon)); + mAddonProxyModel->setFilterRole (Qt::UserRole); + mAddonProxyModel->setDynamicSortFilter (true); + mAddonProxyModel->setSourceModel (mContentModel); + + addonView->setModel(mAddonProxyModel); + + connect(addonView, SIGNAL(clicked(const QModelIndex &)), this, SLOT(slotAddonTableItemClicked(const QModelIndex &))); +} + +void DataFilesPage::buildProfilesView() +{ + profilesComboBox->setPlaceholderText(QString("Select a profile...")); + connect(profilesComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCurrentProfileIndexChanged(int))); +} + +void DataFilesPage::updateViews() +{ + // Ensure the columns are hidden because sort() re-enables them + addonView->setColumnHidden(1, true); + addonView->setColumnHidden(2, true); + addonView->setColumnHidden(3, true); + addonView->setColumnHidden(4, true); + addonView->setColumnHidden(5, true); + addonView->setColumnHidden(6, true); + addonView->setColumnHidden(7, true); + addonView->setColumnHidden(8, true); + addonView->resizeColumnsToContents(); +} + +void ContentSelectorView::ContentSelector::addFiles(const QString &path) +{ + mContentModel->addFiles(path); + //mContentModel->sort(3); // Sort by date accessed + gameFileView->setCurrentIndex(-1); + mContentModel->uncheckAll(); } void DataFilesPage::createActions() @@ -52,17 +128,19 @@ void DataFilesPage::createActions() void DataFilesPage::setupDataFiles() { // Set the encoding to the one found in openmw.cfg or the default - mContentSelector.setEncoding(mGameSettings.value(QString("encoding"), QString("win1252"))); + //mContentSelector.setEncoding(mGameSettings.value(QString("encoding"), QString("win1252"))); QStringList paths = mGameSettings.getDataDirs(); foreach (const QString &path, paths) { - mContentSelector.addFiles(path); + //mContentSelector. + mContentModel->addFiles(path); } QString dataLocal = mGameSettings.getDataLocal(); if (!dataLocal.isEmpty()) - mContentSelector.addFiles(dataLocal); + //mContentSelector. + mContentModel->addFiles(dataLocal); // Sort by date accessed for now //mContentSelector->sort(3); @@ -95,6 +173,7 @@ void DataFilesPage::setupDataFiles() loadSettings(); + gameFileView->setCurrentIndex(-1); } void DataFilesPage::loadSettings() @@ -104,29 +183,17 @@ void DataFilesPage::loadSettings() if (profile.isEmpty()) return; - // mContentSelector.uncheckAll(); + // mContentSelector. + mContentModel->uncheckAll(); - QStringList masters = mLauncherSettings.values(QString("Profiles/") + profile + QString("/master"), Qt::MatchExactly); - QStringList plugins = mLauncherSettings.values(QString("Profiles/") + profile + QString("/plugin"), Qt::MatchExactly); -/* - foreach (const QString &master, masters) { - QModelIndex index = mDataFilesModel->indexFromItem(mDataFilesModel->findItem(master)); - if (index.isValid()) - mDataFilesModel->setCheckState(index, Qt::Checked); - } - - foreach (const QString &plugin, plugins) { - QModelIndex index = mDataFilesModel->indexFromItem(mDataFilesModel->findItem(plugin)); - if (index.isValid()) - mDataFilesModel->setCheckState(index, Qt::Checked); - } - */ + QStringList gameFiles = mLauncherSettings.values(QString("Profiles/") + profile + QString("/master"), Qt::MatchExactly); + QStringList addons = mLauncherSettings.values(QString("Profiles/") + profile + QString("/plugin"), Qt::MatchExactly); } void DataFilesPage::saveSettings() { -// if (mDataFilesModel->rowCount() < 1) -// return; + if (mContentModel->rowCount() < 1) + return; QString profile = mLauncherSettings.value(QString("Profiles/currentprofile")); @@ -141,11 +208,11 @@ void DataFilesPage::saveSettings() mGameSettings.remove(QString("master")); mGameSettings.remove(QString("plugin")); - // EsxModel::EsmFileList items = mDataFilesModel->checkedItems(); -/* - foreach(const EsxModel::EsmFile *item, items) { + ContentSelectorModel::ContentFileList items = mContentModel->checkedItems(); - if (item->masters().size() == 0) { + foreach(const ContentSelectorModel::EsmFile *item, items) { + + if (item->gameFiles().size() == 0) { mLauncherSettings.setMultiValue(QString("Profiles/") + profile + QString("/master"), item->fileName()); mGameSettings.setMultiValue(QString("master"), item->fileName()); @@ -154,7 +221,7 @@ void DataFilesPage::saveSettings() mGameSettings.setMultiValue(QString("plugin"), item->fileName()); } } -*/ + } void DataFilesPage::updateOkButton(const QString &text) @@ -223,23 +290,25 @@ void DataFilesPage::on_deleteProfileAction_triggered() void DataFilesPage::setPluginsCheckstates(Qt::CheckState state) { - if (!pluginView->selectionModel()->hasSelection()) { + if (!addonView->selectionModel()->hasSelection()) { return; } - QModelIndexList indexes = pluginView->selectionModel()->selectedIndexes(); + QModelIndexList indexes = addonView->selectionModel()->selectedIndexes(); foreach (const QModelIndex &index, indexes) { if (!index.isValid()) return; - QModelIndex sourceIndex = mPluginsProxyModel->mapToSource(index); + QModelIndex sourceIndex = mAddonProxyModel->mapToSource(index); if (!sourceIndex.isValid()) return; - //mDataFilesModel->setCheckState(sourceIndex, state); + bool isChecked = ( state == Qt::Checked ); + + mContentModel->setData(sourceIndex, state, Qt::CheckStateRole); } } @@ -287,3 +356,51 @@ void DataFilesPage::profileRenamed(const QString &previous, const QString &curre loadSettings(); } +//////////////////////////// + +QStringList DataFilesPage::checkedItemsPaths() +{ + QStringList itemPaths; + + foreach( const ContentSelectorModel::EsmFile *file, mContentModel->checkedItems()) + itemPaths << file->path(); + + return itemPaths; +} + +void DataFilesPage::slotCurrentProfileIndexChanged(int index) +{ + emit profileChanged(index); +} + +void DataFilesPage::slotCurrentGameFileIndexChanged(int index) +{ + static int oldIndex = -1; + + QAbstractItemModel *const model = gameFileView->model(); + QSortFilterProxyModel *proxy = dynamic_cast(model); + + if (proxy) + proxy->setDynamicSortFilter(false); + + if (oldIndex > -1) + model->setData(model->index(oldIndex, 0), false, Qt::UserRole + 1); + + oldIndex = index; + + model->setData(model->index(index, 0), true, Qt::UserRole + 1); + + if (proxy) + proxy->setDynamicSortFilter(true); +} + +void DataFilesPage::slotAddonTableItemClicked(const QModelIndex &index) +{ + QAbstractItemModel *const model = addonView->model(); + QSortFilterProxyModel *proxy = dynamic_cast(model); + + if (model->data(index, Qt::CheckStateRole).toInt() == Qt::Unchecked) + model->setData(index, Qt::Checked, Qt::CheckStateRole); + else + model->setData(index, Qt::Unchecked, Qt::CheckStateRole); +} diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index ed92da749d..9c7b0538ed 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -5,24 +5,24 @@ #include #include "ui_datafilespage.h" -#include "components/esxselector/view/contentselector.hpp" +#include "components/contentselector/view/contentselector.hpp" class QSortFilterProxyModel; class QAbstractItemModel; class QAction; class QMenu; -class DataFilesModel; class TextInputDialog; class GameSettings; class LauncherSettings; -class PluginsProxyModel; + namespace Files { struct ConfigurationManager; } -class DataFilesPage : public EsxView::ContentSelector +class DataFilesPage : public QWidget, private Ui::DataFilesPage { Q_OBJECT + public: DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gameSettings, LauncherSettings &launcherSettings, QWidget *parent = 0); @@ -42,7 +42,7 @@ public slots: void profileChanged(const QString &previous, const QString ¤t); void profileRenamed(const QString &previous, const QString ¤t); void updateOkButton(const QString &text); - + void updateViews(); // Action slots void on_newProfileAction_triggered(); void on_deleteProfileAction_triggered(); @@ -52,14 +52,16 @@ private slots: private: QMenu *mContextMenu; - ContentSelector mContentSelector; - + //ContentSelectorView::ContentSelector mContentSelector; + ContentSelectorModel::ContentModel *mContentModel; Files::ConfigurationManager &mCfgMgr; GameSettings &mGameSettings; LauncherSettings &mLauncherSettings; TextInputDialog *mNewProfileDialog; + QSortFilterProxyModel *mGameFileProxyModel; + QSortFilterProxyModel *mAddonProxyModel; void setPluginsCheckstates(Qt::CheckState state); @@ -70,6 +72,22 @@ private: void loadSettings(); + ////////////////////////////////////// + void buildContentModel(); + void buildGameFileView(); + void buildAddonView(); + void buildProfilesView(); + + //void addFiles(const QString &path); + + QStringList checkedItemsPaths(); + +private slots: + void slotCurrentProfileIndexChanged(int index); + void slotCurrentGameFileIndexChanged(int index); + void slotAddonTableItemClicked(const QModelIndex &index); + + }; #endif diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index e71fc429fe..4d9ce14d6d 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -17,7 +17,7 @@ #include #include -#include +#include #include "settings/graphicssettings.hpp" diff --git a/apps/launcher/utils/textinputdialog.cpp b/apps/launcher/utils/textinputdialog.cpp index 052fc58e40..51928c09a7 100644 --- a/apps/launcher/utils/textinputdialog.cpp +++ b/apps/launcher/utils/textinputdialog.cpp @@ -7,7 +7,7 @@ #include #include -#include +#include TextInputDialog::TextInputDialog(const QString& title, const QString &text, QWidget *parent) : QDialog(parent) @@ -19,7 +19,7 @@ TextInputDialog::TextInputDialog(const QString& title, const QString &text, QWid // Line edit QValidator *validator = new QRegExpValidator(QRegExp("^[a-zA-Z0-9_]*$"), this); // Alpha-numeric + underscore - mLineEdit = new EsxView::LineEdit(this); + mLineEdit = new ContentSelectorView::LineEdit(this); mLineEdit->setValidator(validator); mLineEdit->setCompleter(0); diff --git a/apps/launcher/utils/textinputdialog.hpp b/apps/launcher/utils/textinputdialog.hpp index 2fb6e0f6b4..de3a9fb723 100644 --- a/apps/launcher/utils/textinputdialog.hpp +++ b/apps/launcher/utils/textinputdialog.hpp @@ -2,11 +2,10 @@ #define TEXTINPUTDIALOG_HPP #include -//#include "lineedit.hpp" class QDialogButtonBox; -namespace EsxView { +namespace ContentSelectorView { class LineEdit; } @@ -16,10 +15,10 @@ class TextInputDialog : public QDialog Q_OBJECT public: explicit TextInputDialog(const QString& title, const QString &text, QWidget *parent = 0); - inline EsxView::LineEdit *lineEdit() { return mLineEdit; } + inline ContentSelectorView::LineEdit *lineEdit() { return mLineEdit; } void setOkButtonEnabled(bool enabled); - EsxView::LineEdit *mLineEdit; + ContentSelectorView::LineEdit *mLineEdit; int exec(); diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 9a6832ec00..ba1dfb57ec 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -79,8 +79,8 @@ void CS::Editor::setupDataFiles() } // Set the charset for reading the esm/esp files - QString encoding = QString::fromStdString(variables["encoding"].as()); - mFileDialog.setEncoding(encoding); + // QString encoding = QString::fromStdString(variables["encoding"].as()); + //mFileDialog.setEncoding(encoding); dataDirs.insert (dataDirs.end(), dataLocal.begin(), dataLocal.end()); diff --git a/apps/opencs/main.cpp b/apps/opencs/main.cpp index ef7123c204..e5e7514ce0 100644 --- a/apps/opencs/main.cpp +++ b/apps/opencs/main.cpp @@ -42,7 +42,7 @@ int main(int argc, char *argv[]) if(!editor.makeIPCServer()) { editor.connectToIPCServer(); - return 0; + // return 0; } return editor.run(); diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index fb031fe5f5..1d6bed7a7c 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -10,24 +10,18 @@ #include #include -#include -#include -#include - -#include - -#include "components/esxselector/model/masterproxymodel.hpp" +#include +#include CSVDoc::FileDialog::FileDialog(QWidget *parent) : ContentSelector(parent) { // Hide the profile elements profileGroupBox->hide(); - pluginView->showColumn(2); + addonView->showColumn(2); resize(400, 400); - // connect(mDataFilesModel, SIGNAL(checkedItemsChanged(QStringList)), this, SLOT(updateOpenButton(QStringList))); connect(projectNameLineEdit, SIGNAL(textChanged(QString)), this, SLOT(updateCreateButton(QString))); connect(projectCreateButton, SIGNAL(clicked()), this, SIGNAL(createNewFile())); diff --git a/apps/opencs/view/doc/filedialog.hpp b/apps/opencs/view/doc/filedialog.hpp index c749099d48..d0c3461b9f 100644 --- a/apps/opencs/view/doc/filedialog.hpp +++ b/apps/opencs/view/doc/filedialog.hpp @@ -4,7 +4,7 @@ #include #include -#include "components/esxselector/view/contentselector.hpp" +#include "components/contentselector/view/contentselector.hpp" #include "ui_datafilespage.h" class QDialogButtonBox; @@ -19,14 +19,14 @@ class QLabel; class DataFilesModel; class PluginsProxyModel; -namespace EsxView +namespace ContentSelectorView { class LineEdit; } namespace CSVDoc { - class FileDialog : public EsxView::ContentSelector + class FileDialog : public ContentSelectorView::ContentSelector { Q_OBJECT public: diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 7703f2d233..963316070a 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -36,7 +36,7 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) // all files/readers used by the engine. This will greaty accelerate // refnumber mangling, as required for handling moved references. int index = ~0; - const std::vector &masters = esm.getMasters(); + const std::vector &masters = esm.getGameFiles(); std::vector *allPlugins = esm.getGlobalReaderList(); for (size_t j = 0; j < masters.size(); j++) { ESM::Header::MasterData &mast = const_cast(masters[j]); diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 34ef43ab5c..7053bb973b 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -79,10 +79,9 @@ set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/datafilespage.ui find_package(Qt4 COMPONENTS QtCore QtGui) if(QT_QTGUI_LIBRARY AND QT_QTCORE_LIBRARY) - add_component_qt_dir (esxselector - model/masterproxymodel model/modelitem - model/pluginsproxymodel model/esmfile model/naturalsort - model/contentmodel + add_component_qt_dir (contentselector + model/modelitem model/esmfile + model/naturalsort model/contentmodel view/profilescombobox view/comboboxlineedit view/lineedit view/contentselector ) diff --git a/components/esxselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp similarity index 64% rename from components/esxselector/model/contentmodel.cpp rename to components/contentselector/model/contentmodel.cpp index d23ea78014..b85da25c67 100644 --- a/components/esxselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -6,7 +6,7 @@ #include #include -EsxModel::ContentModel::ContentModel(QObject *parent) : +ContentSelectorModel::ContentModel::ContentModel(QObject *parent) : QAbstractTableModel(parent), mMimeType ("application/omwcontent"), mMimeTypes (QStringList() << mMimeType), @@ -15,11 +15,11 @@ EsxModel::ContentModel::ContentModel(QObject *parent) : mDefaultFlags (Qt::ItemIsDropEnabled | Qt::ItemIsSelectable), mDropActions (Qt::CopyAction | Qt::MoveAction) { - setEncoding ("win1252"); + // setEncoding ("win1252"); uncheckAll(); } - -void EsxModel::ContentModel::setEncoding(const QString &encoding) +/* +void ContentSelectorModel::ContentModel::setEncoding(const QString &encoding) { if (encoding == QLatin1String("win1252")) mCodec = QTextCodec::codecForName("windows-1252"); @@ -33,8 +33,8 @@ void EsxModel::ContentModel::setEncoding(const QString &encoding) else return; // This should never happen; } - -int EsxModel::ContentModel::columnCount(const QModelIndex &parent) const +*/ +int ContentSelectorModel::ContentModel::columnCount(const QModelIndex &parent) const { if (parent.isValid()) return 0; @@ -42,7 +42,7 @@ int EsxModel::ContentModel::columnCount(const QModelIndex &parent) const return mColumnCount; } -int EsxModel::ContentModel::rowCount(const QModelIndex &parent) const +int ContentSelectorModel::ContentModel::rowCount(const QModelIndex &parent) const { if(parent.isValid()) return 0; @@ -50,7 +50,7 @@ int EsxModel::ContentModel::rowCount(const QModelIndex &parent) const return mFiles.size(); } -const EsxModel::EsmFile* EsxModel::ContentModel::item(int row) const +const ContentSelectorModel::EsmFile *ContentSelectorModel::ContentModel::item(int row) const { if (row >= 0 && row < mFiles.size()) return mFiles.at(row); @@ -58,14 +58,14 @@ const EsxModel::EsmFile* EsxModel::ContentModel::item(int row) const return 0; } -EsxModel::EsmFile *EsxModel::ContentModel::item(int row) +ContentSelectorModel::EsmFile *ContentSelectorModel::ContentModel::item(int row) { if (row >= 0 && row < mFiles.count()) return mFiles.at(row); return 0; } -const EsxModel::EsmFile *EsxModel::ContentModel::findItem(const QString &name) const +const ContentSelectorModel::EsmFile *ContentSelectorModel::ContentModel::findItem(const QString &name) const { foreach (const EsmFile *file, mFiles) { @@ -75,15 +75,18 @@ const EsxModel::EsmFile *EsxModel::ContentModel::findItem(const QString &name) c return 0; } -QModelIndex EsxModel::ContentModel::indexFromItem(EsmFile *item) const +QModelIndex ContentSelectorModel::ContentModel::indexFromItem(const EsmFile *item) const { + //workaround: non-const pointer cast for calls from outside contentmodel/contentselector + EsmFile *non_const_file_ptr = const_cast(item); + if (item) - return index(mFiles.indexOf(item),0); + return index(mFiles.indexOf(non_const_file_ptr),0); return QModelIndex(); } -Qt::ItemFlags EsxModel::ContentModel::flags(const QModelIndex &index) const +Qt::ItemFlags ContentSelectorModel::ContentModel::flags(const QModelIndex &index) const { if (!index.isValid()) return Qt::NoItemFlags; @@ -99,7 +102,7 @@ Qt::ItemFlags EsxModel::ContentModel::flags(const QModelIndex &index) const return mDefaultFlags; } -QVariant EsxModel::ContentModel::data(const QModelIndex &index, int role) const +QVariant ContentSelectorModel::ContentModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); @@ -119,7 +122,7 @@ QVariant EsxModel::ContentModel::data(const QModelIndex &index, int role) const case Qt::EditRole: case Qt::DisplayRole: { - if (column >=0 && column <=EsmFile::FileProperty_Master) + if (column >=0 && column <=EsmFile::FileProperty_GameFile) return file->fileProperty(static_cast(column)); return QVariant(); @@ -154,17 +157,20 @@ QVariant EsxModel::ContentModel::data(const QModelIndex &index, int role) const case Qt::CheckStateRole: { - if (!file->isMaster()) + if (!file->isGameFile()) return isChecked(file->fileName()); break; } case Qt::UserRole: { - if (file->isMaster()) - return "game"; + if (file->isGameFile()) + return ContentType_GameFile; else - return "addon"; + if (flags(index) & Qt::ItemIsEnabled) + return ContentType_Addon; + + break; } case Qt::UserRole + 1: @@ -174,7 +180,7 @@ QVariant EsxModel::ContentModel::data(const QModelIndex &index, int role) const return QVariant(); } -bool EsxModel::ContentModel::setData(const QModelIndex &index, const QVariant &value, int role) +bool ContentSelectorModel::ContentModel::setData(const QModelIndex &index, const QVariant &value, int role) { if(!index.isValid()) return false; @@ -189,11 +195,11 @@ bool EsxModel::ContentModel::setData(const QModelIndex &index, const QVariant &v { QStringList list = value.toStringList(); - for (int i = 0; i < EsmFile::FileProperty_Master; i++) + for (int i = 0; i < EsmFile::FileProperty_GameFile; i++) file->setFileProperty(static_cast(i), list.at(i)); - for (int i = EsmFile::FileProperty_Master; i < list.size(); i++) - file->setFileProperty (EsmFile::FileProperty_Master, list.at(i)); + for (int i = EsmFile::FileProperty_GameFile; i < list.size(); i++) + file->setFileProperty (EsmFile::FileProperty_GameFile, list.at(i)); emit dataChanged(index, index); @@ -209,7 +215,7 @@ bool EsxModel::ContentModel::setData(const QModelIndex &index, const QVariant &v foreach (EsmFile *file, mFiles) { - if (file->masters().contains(fileName)) + if (file->gameFiles().contains(fileName)) { QModelIndex idx = indexFromItem(file); emit dataChanged(idx, idx); @@ -222,15 +228,36 @@ bool EsxModel::ContentModel::setData(const QModelIndex &index, const QVariant &v case Qt::CheckStateRole: { int checkValue = value.toInt(); - + bool success = false; + bool setState = false; if ((checkValue==Qt::Checked) && !isChecked(fileName)) - setCheckState(fileName, true); + { + setState = true; + success = true; + } else if ((checkValue == Qt::Checked) && isChecked (fileName)) - setCheckState(fileName, false); + setState = true; else if (checkValue == Qt::Unchecked) - setCheckState(fileName, false); + setState = true; - emit dataChanged(index, index); + if (setState) + { + setCheckState(fileName, success); + emit dataChanged(index, index); + + } + else + return success; + + + foreach (EsmFile *file, mFiles) + { + if (file->gameFiles().contains(fileName)) + { + QModelIndex idx = indexFromItem(file); + emit dataChanged(idx, idx); + } + } success = true; } @@ -240,7 +267,7 @@ bool EsxModel::ContentModel::setData(const QModelIndex &index, const QVariant &v return success; } -bool EsxModel::ContentModel::insertRows(int position, int rows, const QModelIndex &parent) +bool ContentSelectorModel::ContentModel::insertRows(int position, int rows, const QModelIndex &parent) { if (parent.isValid()) return false; @@ -255,7 +282,7 @@ bool EsxModel::ContentModel::insertRows(int position, int rows, const QModelInde return true; } -bool EsxModel::ContentModel::removeRows(int position, int rows, const QModelIndex &parent) +bool ContentSelectorModel::ContentModel::removeRows(int position, int rows, const QModelIndex &parent) { if (parent.isValid()) return false; @@ -270,17 +297,17 @@ bool EsxModel::ContentModel::removeRows(int position, int rows, const QModelInde return true; } -Qt::DropActions EsxModel::ContentModel::supportedDropActions() const +Qt::DropActions ContentSelectorModel::ContentModel::supportedDropActions() const { return mDropActions; } -QStringList EsxModel::ContentModel::mimeTypes() const +QStringList ContentSelectorModel::ContentModel::mimeTypes() const { return mMimeTypes; } -QMimeData *EsxModel::ContentModel::mimeData(const QModelIndexList &indexes) const +QMimeData *ContentSelectorModel::ContentModel::mimeData(const QModelIndexList &indexes) const { QByteArray encodedData; @@ -298,7 +325,7 @@ QMimeData *EsxModel::ContentModel::mimeData(const QModelIndexList &indexes) cons return mimeData; } -bool EsxModel::ContentModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) +bool ContentSelectorModel::ContentModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { if (action == Qt::IgnoreAction) return true; @@ -325,51 +352,53 @@ bool EsxModel::ContentModel::dropMimeData(const QMimeData *data, Qt::DropAction QString value; QStringList values; - QStringList masters; + QStringList gamefiles; - for (int i = 0; i < EsmFile::FileProperty_Master; ++i) + for (int i = 0; i < EsmFile::FileProperty_GameFile; ++i) { stream >> value; values << value; } - stream >> masters; + stream >> gamefiles; insertRows(beginRow, 1); QModelIndex idx = index(beginRow++, 0, QModelIndex()); - setData(idx, QStringList() << values << masters, Qt::EditRole); + setData(idx, QStringList() << values << gamefiles, Qt::EditRole); } return true; } -bool EsxModel::ContentModel::canBeChecked(const EsmFile *file) const +bool ContentSelectorModel::ContentModel::canBeChecked(const EsmFile *file) const { //element can be checked if all its dependencies are - foreach (const QString &master, file->masters()) - if (!isChecked(master)) + foreach (const QString &gamefile, file->gameFiles()) + if (!isChecked(gamefile)) return false; return true; } -void EsxModel::ContentModel::addFile(EsmFile *file) +void ContentSelectorModel::ContentModel::addFile(EsmFile *file) { beginInsertRows(QModelIndex(), mFiles.count(), mFiles.count()); mFiles.append(file); endInsertRows(); } -void EsxModel::ContentModel::addFiles(const QString &path) +void ContentSelectorModel::ContentModel::addFiles(const QString &path) { QDir dir(path); QStringList filters; filters << "*.esp" << "*.esm" << "*.omwgame" << "*.omwaddon"; dir.setNameFilters(filters); + QTextCodec *codec = QTextCodec::codecForName("UTF8"); + // Create a decoder for non-latin characters in esx metadata - QTextDecoder *decoder = mCodec->makeDecoder(); + QTextDecoder *decoder = codec->makeDecoder(); foreach (const QString &path, dir.entryList()) { @@ -378,16 +407,16 @@ void EsxModel::ContentModel::addFiles(const QString &path) try { ESM::ESMReader fileReader; - ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(QString(mCodec->name()).toStdString())); - fileReader.setEncoder(&encoder); + ToUTF8::Utf8Encoder encoder(); //ToUTF8::calculateEncoding(QString(mCodec->name()).toStdString())); + //fileReader.setEncoder(&encoder); fileReader.open(dir.absoluteFilePath(path).toStdString()); - foreach (const ESM::Header::MasterData &item, fileReader.getMasters()) - file->addMaster(QString::fromStdString(item.name)); + foreach (const ESM::Header::MasterData &item, fileReader.getGameFiles()) + file->addGameFile(QString::fromStdString(item.name)); file->setAuthor (decoder->toUnicode(fileReader.getAuthor().c_str())); file->setDate (info.lastModified()); - file->setVersion (fileReader.getFVer()); + file->setFormat (fileReader.getFormat()); file->setPath (info.absoluteFilePath()); file->setDescription(decoder->toUnicode(fileReader.getDesc().c_str())); @@ -407,7 +436,7 @@ void EsxModel::ContentModel::addFiles(const QString &path) delete decoder; } -bool EsxModel::ContentModel::isChecked(const QString& name) const +bool ContentSelectorModel::ContentModel::isChecked(const QString& name) const { if (mCheckStates.contains(name)) return (mCheckStates[name] == Qt::Checked); @@ -415,7 +444,7 @@ bool EsxModel::ContentModel::isChecked(const QString& name) const return false; } -void EsxModel::ContentModel::setCheckState(const QString &name, bool isChecked) +void ContentSelectorModel::ContentModel::setCheckState(const QString &name, bool isChecked) { if (name.isEmpty()) return; @@ -428,7 +457,7 @@ void EsxModel::ContentModel::setCheckState(const QString &name, bool isChecked) mCheckStates[name] = state; } -EsxModel::ContentFileList EsxModel::ContentModel::checkedItems() const +ContentSelectorModel::ContentFileList ContentSelectorModel::ContentModel::checkedItems() const { ContentFileList list; @@ -441,7 +470,7 @@ EsxModel::ContentFileList EsxModel::ContentModel::checkedItems() const return list; } -void EsxModel::ContentModel::uncheckAll() +void ContentSelectorModel::ContentModel::uncheckAll() { emit layoutAboutToBeChanged(); mCheckStates.clear(); diff --git a/components/esxselector/model/contentmodel.hpp b/components/contentselector/model/contentmodel.hpp similarity index 86% rename from components/esxselector/model/contentmodel.hpp rename to components/contentselector/model/contentmodel.hpp index 6a2dd88ca5..a8ff103da7 100644 --- a/components/esxselector/model/contentmodel.hpp +++ b/components/contentselector/model/contentmodel.hpp @@ -3,19 +3,26 @@ #include #include -namespace EsxModel + +namespace ContentSelectorModel { class EsmFile; typedef QList ContentFileList; + enum ContentType + { + ContentType_GameFile, + ContentType_Addon + }; + class ContentModel : public QAbstractTableModel { Q_OBJECT public: explicit ContentModel(QObject *parent = 0); - void setEncoding(const QString &encoding); + //void setEncoding(const QString &encoding); int rowCount(const QModelIndex &parent = QModelIndex()) const; int columnCount(const QModelIndex &parent = QModelIndex()) const; @@ -34,8 +41,8 @@ namespace EsxModel void addFiles(const QString &path); - QModelIndex indexFromItem(EsmFile *item) const; - const EsxModel::EsmFile *findItem(const QString &name) const; + QModelIndex indexFromItem(const EsmFile *item) const; + const EsmFile *findItem(const QString &name) const; bool isChecked(const QString &name) const; void setCheckState(const QString &name, bool isChecked); diff --git a/components/esxselector/model/esmfile.cpp b/components/contentselector/model/esmfile.cpp similarity index 54% rename from components/esxselector/model/esmfile.cpp rename to components/contentselector/model/esmfile.cpp index bd76dc4a00..9dfe49ebaf 100644 --- a/components/esxselector/model/esmfile.cpp +++ b/components/contentselector/model/esmfile.cpp @@ -3,64 +3,65 @@ #include #include -int EsxModel::EsmFile::sPropertyCount = 7; -QString EsxModel::EsmFile::sToolTip = QString("Author: %1
\ +int ContentSelectorModel::EsmFile::sPropertyCount = 7; +QString ContentSelectorModel::EsmFile::sToolTip = QString("Author: %1
\ Version: %2
\
Description:
%3
\
Dependencies: %4
"); -EsxModel::EsmFile::EsmFile(QString fileName, ModelItem *parent) - : ModelItem(parent), mFileName(fileName), mVersion(0.0f) +ContentSelectorModel::EsmFile::EsmFile(QString fileName, ModelItem *parent) + : ModelItem(parent), mFileName(fileName), mFormat(0) {} -void EsxModel::EsmFile::setFileName(const QString &fileName) + +void ContentSelectorModel::EsmFile::setFileName(const QString &fileName) { mFileName = fileName; } -void EsxModel::EsmFile::setAuthor(const QString &author) +void ContentSelectorModel::EsmFile::setAuthor(const QString &author) { mAuthor = author; } -void EsxModel::EsmFile::setDate(const QDateTime &modified) +void ContentSelectorModel::EsmFile::setDate(const QDateTime &modified) { mModified = modified; } -void EsxModel::EsmFile::setVersion(float version) +void ContentSelectorModel::EsmFile::setFormat(int format) { - mVersion = version; + mFormat = format; } -void EsxModel::EsmFile::setPath(const QString &path) +void ContentSelectorModel::EsmFile::setPath(const QString &path) { mPath = path; } -void EsxModel::EsmFile::setMasters(const QStringList &masters) +void ContentSelectorModel::EsmFile::setGameFiles(const QStringList &gamefiles) { - mMasters = masters; + mGameFiles = gamefiles; } -void EsxModel::EsmFile::setDescription(const QString &description) +void ContentSelectorModel::EsmFile::setDescription(const QString &description) { mDescription = description; } -QByteArray EsxModel::EsmFile::encodedData() const +QByteArray ContentSelectorModel::EsmFile::encodedData() const { QByteArray encodedData; QDataStream stream(&encodedData, QIODevice::WriteOnly); - stream << mFileName << mAuthor << QString::number(mVersion) + stream << mFileName << mAuthor << QString::number(mFormat) << mModified.toString() << mPath << mDescription - << mMasters; + << mGameFiles; return encodedData; } -QVariant EsxModel::EsmFile::fileProperty(const FileProperty prop) const +QVariant ContentSelectorModel::EsmFile::fileProperty(const FileProperty prop) const { switch (prop) { @@ -72,8 +73,8 @@ QVariant EsxModel::EsmFile::fileProperty(const FileProperty prop) const return mAuthor; break; - case FileProperty_Version: - return mVersion; + case FileProperty_Format: + return mFormat; break; case FileProperty_DateModified: @@ -88,8 +89,8 @@ QVariant EsxModel::EsmFile::fileProperty(const FileProperty prop) const return mDescription; break; - case FileProperty_Master: - return mMasters; + case FileProperty_GameFile: + return mGameFiles; break; default: @@ -97,7 +98,7 @@ QVariant EsxModel::EsmFile::fileProperty(const FileProperty prop) const } return QVariant(); } -void EsxModel::EsmFile::setFileProperty (const FileProperty prop, const QString &value) +void ContentSelectorModel::EsmFile::setFileProperty (const FileProperty prop, const QString &value) { switch (prop) { @@ -109,8 +110,8 @@ void EsxModel::EsmFile::setFileProperty (const FileProperty prop, const QString mAuthor = value; break; - case FileProperty_Version: - mVersion = value.toFloat(); + case FileProperty_Format: + mFormat = value.toInt(); break; case FileProperty_DateModified: @@ -125,8 +126,8 @@ void EsxModel::EsmFile::setFileProperty (const FileProperty prop, const QString mDescription = value; break; - case FileProperty_Master: - mMasters << value; + case FileProperty_GameFile: + mGameFiles << value; break; default: diff --git a/components/esxselector/model/esmfile.hpp b/components/contentselector/model/esmfile.hpp similarity index 58% rename from components/esxselector/model/esmfile.hpp rename to components/contentselector/model/esmfile.hpp index 4f6d7a6244..743b1c5a68 100644 --- a/components/esxselector/model/esmfile.hpp +++ b/components/contentselector/model/esmfile.hpp @@ -8,7 +8,7 @@ class QMimeData; -namespace EsxModel +namespace ContentSelectorModel { class EsmFile : public ModelItem { @@ -21,11 +21,11 @@ namespace EsxModel { FileProperty_FileName = 0, FileProperty_Author = 1, - FileProperty_Version = 2, + FileProperty_Format = 2, FileProperty_DateModified = 3, FileProperty_Path = 4, FileProperty_Description = 5, - FileProperty_Master = 6 + FileProperty_GameFile = 6 }; EsmFile(QString fileName = QString(), ModelItem *parent = 0); @@ -40,28 +40,28 @@ namespace EsxModel void setAuthor(const QString &author); void setSize(const int size); void setDate(const QDateTime &modified); - void setVersion(const float version); + void setFormat(const int format); void setPath(const QString &path); - void setMasters(const QStringList &masters); + void setGameFiles(const QStringList &gameFiles); void setDescription(const QString &description); - inline void addMaster (const QString &name) {mMasters.append(name); } + inline void addGameFile (const QString &name) {mGameFiles.append(name); } QVariant fileProperty (const FileProperty prop) const; - inline QString fileName() const { return mFileName; } - inline QString author() const { return mAuthor; } - inline QDateTime modified() const { return mModified; } - inline float version() const { return mVersion; } - inline QString path() const { return mPath; } - inline const QStringList &masters() const { return mMasters; } - inline QString description() const { return mDescription; } - inline QString toolTip() const { return sToolTip.arg(mAuthor) - .arg(mVersion) + inline QString fileName() const { return mFileName; } + inline QString author() const { return mAuthor; } + inline QDateTime modified() const { return mModified; } + inline float format() const { return mFormat; } + inline QString path() const { return mPath; } + inline const QStringList &gameFiles() const { return mGameFiles; } + inline QString description() const { return mDescription; } + inline QString toolTip() const { return sToolTip.arg(mAuthor) + .arg(mFormat) .arg(mDescription) - .arg(mMasters.join(", ")); - } + .arg(mGameFiles.join(", ")); + } - inline bool isMaster() const { return (mMasters.size() == 0); } + inline bool isGameFile() const { return (mGameFiles.size() == 0); } QByteArray encodedData() const; public: @@ -73,15 +73,13 @@ namespace EsxModel QString mFileName; QString mAuthor; QDateTime mModified; - float mVersion; + int mFormat; QString mPath; - QStringList mMasters; + QStringList mGameFiles; QString mDescription; QString mToolTip; }; } -Q_DECLARE_METATYPE (EsxModel::EsmFile *) - #endif diff --git a/components/contentselector/model/modelitem.cpp b/components/contentselector/model/modelitem.cpp new file mode 100644 index 0000000000..e1d737c2d4 --- /dev/null +++ b/components/contentselector/model/modelitem.cpp @@ -0,0 +1,69 @@ +#include "modelitem.hpp" + +ContentSelectorModel::ModelItem::ModelItem(ModelItem *parent) + : mParentItem(parent) +{ +} +/* +ContentSelectorModel::ModelItem::ModelItem(const ModelItem *parent) + // : mParentItem(parent) +{ +} +*/ + +ContentSelectorModel::ModelItem::~ModelItem() +{ + qDeleteAll(mChildItems); +} + + +ContentSelectorModel::ModelItem *ContentSelectorModel::ModelItem::parent() const +{ + return mParentItem; +} + +bool ContentSelectorModel::ModelItem::hasFormat(const QString &mimetype) const +{ + if (mimetype == "application/omwcontent") + return true; + + return QMimeData::hasFormat(mimetype); +} +int ContentSelectorModel::ModelItem::row() const +{ + if (mParentItem) + return 1; + //return mParentItem->childRow(const_cast(this)); + //return mParentItem->mChildItems.indexOf(const_cast(this)); + + return -1; +} + + +int ContentSelectorModel::ModelItem::childCount() const +{ + return mChildItems.count(); +} + +int ContentSelectorModel::ModelItem::childRow(ModelItem *child) const +{ + Q_ASSERT(child); + + return mChildItems.indexOf(child); +} + +ContentSelectorModel::ModelItem *ContentSelectorModel::ModelItem::child(int row) +{ + return mChildItems.value(row); +} + + +void ContentSelectorModel::ModelItem::appendChild(ModelItem *item) +{ + mChildItems.append(item); +} + +void ContentSelectorModel::ModelItem::removeChild(int row) +{ + mChildItems.removeAt(row); +} diff --git a/components/esxselector/model/modelitem.hpp b/components/contentselector/model/modelitem.hpp similarity index 96% rename from components/esxselector/model/modelitem.hpp rename to components/contentselector/model/modelitem.hpp index 5ee5e417ed..57214b09cf 100644 --- a/components/esxselector/model/modelitem.hpp +++ b/components/contentselector/model/modelitem.hpp @@ -4,7 +4,7 @@ #include #include -namespace EsxModel +namespace ContentSelectorModel { class ModelItem : public QMimeData { diff --git a/components/esxselector/model/naturalsort.cpp b/components/contentselector/model/naturalsort.cpp similarity index 100% rename from components/esxselector/model/naturalsort.cpp rename to components/contentselector/model/naturalsort.cpp diff --git a/components/esxselector/model/naturalsort.hpp b/components/contentselector/model/naturalsort.hpp similarity index 100% rename from components/esxselector/model/naturalsort.hpp rename to components/contentselector/model/naturalsort.hpp diff --git a/components/esxselector/view/comboboxlineedit.cpp b/components/contentselector/view/comboboxlineedit.cpp similarity index 83% rename from components/esxselector/view/comboboxlineedit.cpp rename to components/contentselector/view/comboboxlineedit.cpp index 815a1130b5..df647a4a09 100644 --- a/components/esxselector/view/comboboxlineedit.cpp +++ b/components/contentselector/view/comboboxlineedit.cpp @@ -3,7 +3,7 @@ #include "comboboxlineedit.hpp" -EsxView::ComboBoxLineEdit::ComboBoxLineEdit(QWidget *parent) +ContentSelectorView::ComboBoxLineEdit::ComboBoxLineEdit(QWidget *parent) : QLineEdit(parent) { mClearButton = new QToolButton(this); @@ -21,7 +21,7 @@ EsxView::ComboBoxLineEdit::ComboBoxLineEdit(QWidget *parent) setStyleSheet(QString("ComboBoxLineEdit { background-color: transparent; padding-right: %1px; } ").arg(mClearButton->sizeHint().width() + frameWidth + 1)); } -void EsxView::ComboBoxLineEdit::resizeEvent(QResizeEvent *) +void ContentSelectorView::ComboBoxLineEdit::resizeEvent(QResizeEvent *) { QSize sz = mClearButton->sizeHint(); int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); @@ -29,7 +29,7 @@ void EsxView::ComboBoxLineEdit::resizeEvent(QResizeEvent *) (rect().bottom() + 1 - sz.height())/2); } -void EsxView::ComboBoxLineEdit::updateClearButton(const QString& text) +void ContentSelectorView::ComboBoxLineEdit::updateClearButton(const QString& text) { mClearButton->setVisible(!text.isEmpty()); } diff --git a/components/esxselector/view/comboboxlineedit.hpp b/components/contentselector/view/comboboxlineedit.hpp similarity index 96% rename from components/esxselector/view/comboboxlineedit.hpp rename to components/contentselector/view/comboboxlineedit.hpp index f3b251955d..1aef2f57b6 100644 --- a/components/esxselector/view/comboboxlineedit.hpp +++ b/components/contentselector/view/comboboxlineedit.hpp @@ -14,7 +14,7 @@ class QToolButton; -namespace EsxView +namespace ContentSelectorView { class ComboBoxLineEdit : public QLineEdit { diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp new file mode 100644 index 0000000000..615e9a846d --- /dev/null +++ b/components/contentselector/view/contentselector.cpp @@ -0,0 +1,134 @@ +#include "contentselector.hpp" + +#include "../model/contentmodel.hpp" +#include "../model/esmfile.hpp" + +#include + +#include +#include +#include + +ContentSelectorView::ContentSelector::ContentSelector(QWidget *parent) : + QDialog(parent) +{ + setupUi(this); + + buildContentModel(); + buildGameFileView(); + buildAddonView(); + buildProfilesView(); + + updateViews(); + +} + +void ContentSelectorView::ContentSelector::buildContentModel() +{ + mContentModel = new ContentSelectorModel::ContentModel(); + connect(mContentModel, SIGNAL(layoutChanged()), this, SLOT(updateViews())); +} + +void ContentSelectorView::ContentSelector::buildGameFileView() +{ + mGameFileProxyModel = new QSortFilterProxyModel(this); + mGameFileProxyModel->setFilterRegExp(QString::number((int)ContentSelectorModel::ContentType_GameFile)); + mGameFileProxyModel->setFilterRole (Qt::UserRole); + mGameFileProxyModel->setSourceModel (mContentModel); + + gameFileView->setPlaceholderText(QString("Select a game file...")); + gameFileView->setModel(mGameFileProxyModel); + + connect(gameFileView, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCurrentGameFileIndexChanged(int))); + + gameFileView->setCurrentIndex(-1); + gameFileView->setCurrentIndex(0); +} + +void ContentSelectorView::ContentSelector::buildAddonView() +{ + mAddonProxyModel = new QSortFilterProxyModel(this); + mAddonProxyModel->setFilterRegExp (QString::number((int)ContentSelectorModel::ContentType_Addon)); + mAddonProxyModel->setFilterRole (Qt::UserRole); + mAddonProxyModel->setDynamicSortFilter (true); + mAddonProxyModel->setSourceModel (mContentModel); + + addonView->setModel(mAddonProxyModel); + + connect(addonView, SIGNAL(clicked(const QModelIndex &)), this, SLOT(slotAddonTableItemClicked(const QModelIndex &))); +} + +void ContentSelectorView::ContentSelector::buildProfilesView() +{ + profilesComboBox->setPlaceholderText(QString("Select a profile...")); + connect(profilesComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCurrentProfileIndexChanged(int))); +} + +void ContentSelectorView::ContentSelector::updateViews() +{ + // Ensure the columns are hidden because sort() re-enables them + addonView->setColumnHidden(1, true); + addonView->setColumnHidden(2, true); + addonView->setColumnHidden(3, true); + addonView->setColumnHidden(4, true); + addonView->setColumnHidden(5, true); + addonView->setColumnHidden(6, true); + addonView->setColumnHidden(7, true); + addonView->setColumnHidden(8, true); + addonView->resizeColumnsToContents(); +} + +void ContentSelectorView::ContentSelector::addFiles(const QString &path) +{ + mContentModel->addFiles(path); + //mContentModel->sort(3); // Sort by date accessed + gameFileView->setCurrentIndex(-1); + mContentModel->uncheckAll(); +} + +QStringList ContentSelectorView::ContentSelector::checkedItemsPaths() +{ + QStringList itemPaths; + + foreach( const ContentSelectorModel::EsmFile *file, mContentModel->checkedItems()) + itemPaths << file->path(); + + return itemPaths; +} + +void ContentSelectorView::ContentSelector::slotCurrentProfileIndexChanged(int index) +{ + emit profileChanged(index); +} + +void ContentSelectorView::ContentSelector::slotCurrentGameFileIndexChanged(int index) +{ + static int oldIndex = -1; + + QAbstractItemModel *const model = gameFileView->model(); + QSortFilterProxyModel *proxy = dynamic_cast(model); + + if (proxy) + proxy->setDynamicSortFilter(false); + + if (oldIndex > -1) + model->setData(model->index(oldIndex, 0), false, Qt::UserRole + 1); + + oldIndex = index; + + model->setData(model->index(index, 0), true, Qt::UserRole + 1); + + if (proxy) + proxy->setDynamicSortFilter(true); +} + +void ContentSelectorView::ContentSelector::slotAddonTableItemClicked(const QModelIndex &index) +{ + QAbstractItemModel *const model = addonView->model(); + QSortFilterProxyModel *proxy = dynamic_cast(model); + + if (model->data(index, Qt::CheckStateRole).toInt() == Qt::Unchecked) + model->setData(index, Qt::Checked, Qt::CheckStateRole); + else + model->setData(index, Qt::Unchecked, Qt::CheckStateRole); +} diff --git a/components/esxselector/view/contentselector.hpp b/components/contentselector/view/contentselector.hpp similarity index 51% rename from components/esxselector/view/contentselector.hpp rename to components/contentselector/view/contentselector.hpp index e074fe6880..8032b04495 100644 --- a/components/esxselector/view/contentselector.hpp +++ b/components/contentselector/view/contentselector.hpp @@ -5,15 +5,11 @@ #include "ui_datafilespage.h" -namespace EsxModel -{ - class ContentModel; - class DataFilesModel; -} +namespace ContentSelectorModel { class ContentModel; } class QSortFilterProxyModel; -namespace EsxView +namespace ContentSelectorView { class ContentSelector : public QDialog, protected Ui::DataFilesPage { @@ -21,27 +17,25 @@ namespace EsxView protected: - EsxModel::DataFilesModel *mDataFilesModel; - EsxModel::ContentModel *mContentModel; - QSortFilterProxyModel *mMasterProxyModel; - QSortFilterProxyModel *mPluginsProxyModel; + ContentSelectorModel::ContentModel *mContentModel; + QSortFilterProxyModel *mGameFileProxyModel; + QSortFilterProxyModel *mAddonProxyModel; public: + explicit ContentSelector(QWidget *parent = 0); - void buildModelsAndViews(); + static ContentSelector &cast(QWidget *subject); //static constructor function for singleton performance. void addFiles(const QString &path); - void setEncoding(const QString &encoding); - void setPluginCheckState(); void setCheckState(QModelIndex index, QSortFilterProxyModel *model); QStringList checkedItemsPaths(); - void on_checkAction_triggered(); private: - void buildSourceModel(); - void buildMasterView(); - void buildPluginsView(); + + void buildContentModel(); + void buildGameFileView(); + void buildAddonView(); void buildProfilesView(); signals: @@ -50,8 +44,8 @@ namespace EsxView private slots: void updateViews(); void slotCurrentProfileIndexChanged(int index); - void slotCurrentMasterIndexChanged(int index); - void slotPluginTableItemClicked(const QModelIndex &index); + void slotCurrentGameFileIndexChanged(int index); + void slotAddonTableItemClicked(const QModelIndex &index); }; } diff --git a/components/esxselector/view/lineedit.cpp b/components/contentselector/view/lineedit.cpp similarity index 87% rename from components/esxselector/view/lineedit.cpp rename to components/contentselector/view/lineedit.cpp index 48be2f022f..b6fdfa805c 100644 --- a/components/esxselector/view/lineedit.cpp +++ b/components/contentselector/view/lineedit.cpp @@ -4,7 +4,7 @@ #include "lineedit.hpp" -EsxView::LineEdit::LineEdit(QWidget *parent) +ContentSelectorView::LineEdit::LineEdit(QWidget *parent) : QLineEdit(parent) { mClearButton = new QToolButton(this); @@ -25,7 +25,7 @@ EsxView::LineEdit::LineEdit(QWidget *parent) qMax(msz.height(), mClearButton->sizeHint().height() + frameWidth * 2 + 2)); } -void EsxView::LineEdit::resizeEvent(QResizeEvent *) +void ContentSelectorView::LineEdit::resizeEvent(QResizeEvent *) { QSize sz = mClearButton->sizeHint(); int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); @@ -33,7 +33,7 @@ void EsxView::LineEdit::resizeEvent(QResizeEvent *) (rect().bottom() + 1 - sz.height())/2); } -void EsxView::LineEdit::updateClearButton(const QString& text) +void ContentSelectorView::LineEdit::updateClearButton(const QString& text) { mClearButton->setVisible(!text.isEmpty()); } diff --git a/components/esxselector/view/lineedit.hpp b/components/contentselector/view/lineedit.hpp similarity index 96% rename from components/esxselector/view/lineedit.hpp rename to components/contentselector/view/lineedit.hpp index 4e0cbe3390..ab1c37203a 100644 --- a/components/esxselector/view/lineedit.hpp +++ b/components/contentselector/view/lineedit.hpp @@ -14,7 +14,7 @@ class QToolButton; -namespace EsxView +namespace ContentSelectorView { class LineEdit : public QLineEdit { diff --git a/components/esxselector/view/profilescombobox.cpp b/components/contentselector/view/profilescombobox.cpp similarity index 83% rename from components/esxselector/view/profilescombobox.cpp rename to components/contentselector/view/profilescombobox.cpp index 0d709aa50c..cb0ba7b77e 100644 --- a/components/esxselector/view/profilescombobox.cpp +++ b/components/contentselector/view/profilescombobox.cpp @@ -7,7 +7,7 @@ #include "profilescombobox.hpp" #include "comboboxlineedit.hpp" -EsxView::ProfilesComboBox::ProfilesComboBox(QWidget *parent) : +ContentSelectorView::ProfilesComboBox::ProfilesComboBox(QWidget *parent) : QComboBox(parent) { mValidator = new QRegExpValidator(QRegExp("^[a-zA-Z0-9_]*$"), this); // Alpha-numeric + underscore @@ -21,7 +21,7 @@ EsxView::ProfilesComboBox::ProfilesComboBox(QWidget *parent) : setInsertPolicy(QComboBox::NoInsert); } -void EsxView::ProfilesComboBox::setEditEnabled(bool editable) +void ContentSelectorView::ProfilesComboBox::setEditEnabled(bool editable) { if (isEditable() == editable) return; @@ -47,7 +47,7 @@ void EsxView::ProfilesComboBox::setEditEnabled(bool editable) SLOT(slotTextChanged(QString))); } -void EsxView::ProfilesComboBox::slotTextChanged(const QString &text) +void ContentSelectorView::ProfilesComboBox::slotTextChanged(const QString &text) { QPalette *palette = new QPalette(); palette->setColor(QPalette::Text,Qt::red); @@ -61,7 +61,7 @@ void EsxView::ProfilesComboBox::slotTextChanged(const QString &text) } } -void EsxView::ProfilesComboBox::slotEditingFinished() +void ContentSelectorView::ProfilesComboBox::slotEditingFinished() { QString current = currentText(); QString previous = itemText(currentIndex()); @@ -82,7 +82,7 @@ void EsxView::ProfilesComboBox::slotEditingFinished() emit(profileRenamed(previous, current)); } -void EsxView::ProfilesComboBox::slotIndexChanged(int index) +void ContentSelectorView::ProfilesComboBox::slotIndexChanged(int index) { if (index == -1) return; @@ -91,7 +91,7 @@ void EsxView::ProfilesComboBox::slotIndexChanged(int index) mOldProfile = itemText(index); } -void EsxView::ProfilesComboBox::paintEvent(QPaintEvent *) +void ContentSelectorView::ProfilesComboBox::paintEvent(QPaintEvent *) { QStylePainter painter(this); painter.setPen(palette().color(QPalette::Text)); @@ -107,7 +107,7 @@ void EsxView::ProfilesComboBox::paintEvent(QPaintEvent *) painter.drawControl(QStyle::CE_ComboBoxLabel, opt); } -void EsxView::ProfilesComboBox::setPlaceholderText(const QString &text) +void ContentSelectorView::ProfilesComboBox::setPlaceholderText(const QString &text) { mPlaceholderText = text; } diff --git a/components/esxselector/view/profilescombobox.hpp b/components/contentselector/view/profilescombobox.hpp similarity index 96% rename from components/esxselector/view/profilescombobox.hpp rename to components/contentselector/view/profilescombobox.hpp index 28740783ba..d81c1e6a5a 100644 --- a/components/esxselector/view/profilescombobox.hpp +++ b/components/contentselector/view/profilescombobox.hpp @@ -6,7 +6,7 @@ class QString; class QRegExpValidator; -namespace EsxView +namespace ContentSelectorView { class ProfilesComboBox : public QComboBox { diff --git a/components/esm/esmreader.hpp b/components/esm/esmreader.hpp index edc724cd2a..3bf194c4e3 100644 --- a/components/esm/esmreader.hpp +++ b/components/esm/esmreader.hpp @@ -34,7 +34,7 @@ public: float getFVer() const { if(mHeader.mData.version == VER_12) return 1.2; else return 1.3; } const std::string getAuthor() const { return mHeader.mData.author.toString(); } const std::string getDesc() const { return mHeader.mData.desc.toString(); } - const std::vector &getMasters() const { return mHeader.mMaster; } + const std::vector &getGameFiles() const { return mHeader.mMaster; } int getFormat() const; const NAME &retSubName() const { return mCtx.subName; } uint32_t getSubSize() const { return mCtx.leftSub; } diff --git a/components/esm/loadcell.cpp b/components/esm/loadcell.cpp index d8d0c12912..9793391ed8 100644 --- a/components/esm/loadcell.cpp +++ b/components/esm/loadcell.cpp @@ -172,7 +172,7 @@ bool Cell::getNextRef(ESMReader &esm, CellRef &ref) // If the most significant 8 bits are used, then this reference already exists. // In this case, do not spawn a new reference, but overwrite the old one. ref.mRefnum &= 0x00ffffff; // delete old plugin ID - const std::vector &masters = esm.getMasters(); + const std::vector &masters = esm.getGameFiles(); global = masters[local-1].index + 1; ref.mRefnum |= global << 24; // insert global plugin ID } @@ -276,7 +276,7 @@ bool Cell::getNextMVRF(ESMReader &esm, MovedCellRef &mref) int local = (mref.mRefnum & 0xff000000) >> 24; size_t global = esm.getIndex() + 1; mref.mRefnum &= 0x00ffffff; // delete old plugin ID - const std::vector &masters = esm.getMasters(); + const std::vector &masters = esm.getGameFiles(); global = masters[local-1].index + 1; mref.mRefnum |= global << 24; // insert global plugin ID diff --git a/components/esxselector/model/datafilesmodel.cpp b/components/esxselector/model/datafilesmodel.cpp deleted file mode 100644 index c98f70b162..0000000000 --- a/components/esxselector/model/datafilesmodel.cpp +++ /dev/null @@ -1,608 +0,0 @@ -#include -#include -#include -#include -#include - -#include - -#include - -#include "esmfile.hpp" - -#include "datafilesmodel.hpp" - -#include - -EsxModel::DataFilesModel::DataFilesModel(QObject *parent) : - QAbstractTableModel(parent) -{ - mEncoding = QString("win1252"); -} - -EsxModel::DataFilesModel::~DataFilesModel() -{ -} - -int EsxModel::DataFilesModel::rowCount(const QModelIndex &parent) const -{ - return parent.isValid() ? 0 : mFiles.count(); -} - -int EsxModel::DataFilesModel::columnCount(const QModelIndex &parent) const -{ - return parent.isValid() ? 0 : 1; -} - -const EsxModel::EsmFile* EsxModel::DataFilesModel::findItem(const QString &name) -{ - for (int i = 0; i < mFiles.size(); ++i) - { - const EsmFile *file = item(i); - - if (name == file->fileName()) - return file; - } - - return 0; -} - -const EsxModel::EsmFile* EsxModel::DataFilesModel::item(int row) const -{ - if (row >= 0 && row < mFiles.count()) - return mFiles.at(row); - - return 0; -} - -Qt::ItemFlags EsxModel::DataFilesModel::flags(const QModelIndex &index) const -{ - if (!index.isValid()) - return Qt::NoItemFlags; - - const EsmFile *file = item(index.row()); - - if (!file) - return Qt::NoItemFlags; - - Qt::ItemFlags dragDropFlags = Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; - Qt::ItemFlags checkFlags = Qt::ItemIsUserCheckable | Qt::ItemIsEditable; - Qt::ItemFlags defaultFlags = Qt::ItemIsDropEnabled | Qt::ItemIsSelectable; - - if (canBeChecked(file)) - return defaultFlags | dragDropFlags | checkFlags | Qt::ItemIsEnabled; - else - return defaultFlags; -} - -QVariant EsxModel::DataFilesModel::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) - return QVariant(); - - if (index.row() >= mFiles.size()) - return QVariant(); - - const EsmFile *file = item(index.row()); - - if (!file) - return QVariant(); - - const int column = index.column(); - - switch (role) - { - case Qt::EditRole: - case Qt::DisplayRole: - { - - switch (column) - { - case 0: - return file->fileName(); - case 1: - return file->author(); - case 2: - return file->modified().toString(Qt::ISODate); - case 3: - return file->version(); - case 4: - return file->path(); - case 5: - return file->masters().join(", "); - case 6: - return file->description(); - } - break; - } - - case Qt::TextAlignmentRole: - { - switch (column) - { - case 0: - case 1: - return Qt::AlignLeft + Qt::AlignVCenter; - case 2: - case 3: - return Qt::AlignRight + Qt::AlignVCenter; - default: - return Qt::AlignLeft + Qt::AlignVCenter; - } - break; - } - - case Qt::ToolTipRole: - { - if (column != 0) - return QVariant(); - - if (file->version() == 0.0f) - return QVariant(); // Data not set - - QString tooltip = - QString("Author: %1
\ - Version: %2
\ -
Description:
%3
\ -
Dependencies: %4
") - .arg(file->author()) - .arg(QString::number(file->version())) - .arg(file->description()) - .arg(file->masters().join(", ")); - - - return tooltip; - break; - } - - case Qt::UserRole: - { - if (file->masters().size() == 0) - return "game"; - else - return "addon"; - - break; - } - - case Qt::UserRole + 1: - //return check state here - break; - - default: - return QVariant(); - break; - } - -} - -bool EsxModel::DataFilesModel::setData(const QModelIndex &index, const QVariant &value, int role) -{ - if (!index.isValid()) - return false; - - switch (role) - { - case Qt::EditRole: - { - const EsmFile *file = item(index.row()); - - // iterate loop to repopulate file pointer with data in string list. - QStringList list = value.toStringList(); - for (int i = 0; i <999; ++i) - { - file->setProperty(i, value.at(i)); - } - - //populate master list here (emit data changed for each master and - //each item (other than the dropped item) which share each of the masters - file->masters().append(masterList); - - emit dataChanged(index, index); - return true; - } - break; - - case Qt::UserRole + 1: - { - EsmFile *file = item(index.row()); - //set file's checkstate to the passed checkstate - emit dataChanged(index, index); - - for (int i = 0; i < mFiles.size(); ++i) - if (mFiles.at(i)->getMasters().contains(file->fileName())) - emit dataChanged(QAbstractTableModel::index(i,0), QAbstractTableModel::index(i,0)); - - return true; - } - break; - - case Qt::CheckStateRole: - { - EsmFile *file = item(index.row()); - - if ((value.toInt() == Qt::Checked) && !file->isChecked()) - file->setChecked(true); - else if (value.toInt() == Qt::Checked && file->isChecked()) - file->setChecked(false); - else if (value.toInt() == Qt::UnChecked) - file->setChecked(false); - - emit dataChanged(index, index); - - return true; - } - break; - } - return false; -} - -bool EsxModel::DataFilesModel::insertRows(int row, int count, const QModelIndex &parent) -{ - if (parent.isValid()) - return false; - - beginInsertRows(QModelIndex(),row, row+count-1); - { - for (int i = 0; i < count; ++i) - mFiles.insert(row, new EsmFile()); - } endInsertRows(); - - return true; -} - -bool EsxModel::DataFilesModel::removeRows(int row, int count, const QModelIndex &parent) -{ - if (parent.isValid()) - return false; - - beginRemoveRows(QModelIndex(), row, row+count-1); - { - for (int i = 0; i < count; ++i) - delete mFiles.takeAt(row); - } endRemoveRows(); - - return true; -} - -Qt::DropActions EsxModel::DataFilesModel::supportedDropActions() const -{ - return Qt::CopyAction | Qt::MoveAction; -} - -QStringList EsxModel::DataFilesModel::mimeTypes() const -{ - QStringList types; - types << "application/omwcontent"; - return types; -} - -QMimeData *EsxModel::DataFilesModel::mimeData(const QModelIndexList &indexes) const -{ - QMimeData *mimeData = new QMimeData(); - QByteArray encodedData; - - QDataStream stream (&encodedData, QIODevice::WriteOnly); - - foreach (const QModelIndex &index, indexes) - { - if (index.isValid()) - { - EsmFile *file = item (index.row()); - - for (int i = 0; i < file->propertyCount(); ++i) - stream << data(index, Qt::DisplayRole).toString(); - - EsmFile *file = item(index.row()); - stream << file->getMasters(); - } - } - - mimeData->setData("application/omwcontent", encodedData); - - return mimeData; -} - -bool EsxModel::DataFilesModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) -{ - if (action == Qt::IgnoreAction) - return true; - - if (action != Qt::MoveAction) - return false; - - if (!data->hasFormat("application/omwcontent")) - return false; - - int dropRow = row; - - if (dropRow == -1) - { - if (parent.isValid()) - dropRow = parent.row(); - else - dropRow = rowCount(QModelIndex()); - } - - if (parent.isValid()) - qDebug() << "parent: " << parent.data().toString(); - qDebug() << "dragged file: " << (qobject_cast(data))->fileName(); -// qDebug() << "inserting file: " << droppedfile->fileName() << " ahead of " << file->fileName(); - insertRows (dropRow, 1, QModelIndex()); - - - const EsmFile *draggedFile = qobject_cast(data); - - int dragRow = -1; - - for (int i = 0; i < mFiles.size(); ++i) - if (draggedFile->fileName() == mFiles.at(i)->fileName()) - { - dragRow = i; - break; - } - - for (int i = 0; i < mFiles.count(); ++i) - { - qDebug() << "index: " << i << "file: " << item(i)->fileName(); - qDebug() << mFiles.at(i)->fileName(); - } - - qDebug() << "drop row: " << dropRow << "; drag row: " << dragRow; -// const EsmFile *file = qobject_cast(data); - // int index = mFiles.indexOf(file); - //qDebug() << "file name: " << file->fileName() << "; index: " << index; - mFiles.swap(dropRow, dragRow); - //setData(index(startRow, 0), varFile); - emit dataChanged(index(0,0), index(rowCount(),0)); - return true; -} - -void EsxModel::DataFilesModel::setEncoding(const QString &encoding) -{ - mEncoding = encoding; -} - -void EsxModel::DataFilesModel::setCheckState(const QModelIndex &index, Qt::CheckState state) -{ - if (!index.isValid()) - return; - - QString name = item(index.row())->fileName(); - mCheckStates[name] = state; - - // Force a redraw of the view since unchecking one item can affect another - QModelIndex firstIndex = indexFromItem(mFiles.first()); - QModelIndex lastIndex = indexFromItem(mFiles.last()); - - emit dataChanged(firstIndex, lastIndex); - emit checkedItemsChanged(checkedItems()); - -} - -Qt::CheckState EsxModel::DataFilesModel::checkState(const QModelIndex &index) -{ - return mCheckStates[item(index.row())->fileName()]; -} - -bool EsxModel::DataFilesModel::moveRow(int oldrow, int row, const QModelIndex &parent) -{ - if (oldrow < 0 || row < 0 || oldrow == row) - return false; - - emit layoutAboutToBeChanged(); - //emit beginMoveRows(parent, oldrow, oldrow, parent, row); - mFiles.swap(oldrow, row); - //emit endInsertRows(); - emit layoutChanged(); - - return true; -} - -QVariant EsxModel::DataFilesModel::headerData(int section, Qt::Orientation orientation, int role) const -{ - if (role != Qt::DisplayRole) - return QVariant(); - - if (orientation == Qt::Horizontal) { - switch (section) { - case 0: return tr("Name"); - case 1: return tr("Author"); - case 2: return tr("Size"); - case 3: return tr("Modified"); - case 4: return tr("Accessed"); - case 5: return tr("Version"); - case 6: return tr("Path"); - case 7: return tr("Masters"); - case 8: return tr("Description"); - } - } - return QVariant(); -} - -//!!!!!!!!!!!!!!!!!!!!!!! -bool lessThanEsmFile(const EsxModel::EsmFile *e1, const EsxModel::EsmFile *e2) -{ - //Masters first then alphabetically - if (e1->fileName().endsWith(".esm") && !e2->fileName().endsWith(".esm")) - return true; - if (!e1->fileName().endsWith(".esm") && e2->fileName().endsWith(".esm")) - return false; - - return e1->fileName().toLower() < e2->fileName().toLower(); -} -//!!!!!!!!!!!!!!!!!!!!!!! -bool lessThanDate(const EsxModel::EsmFile *e1, const EsxModel::EsmFile *e2) -{ - if (e1->modified().toString(Qt::ISODate) < e2->modified().toString(Qt::ISODate)) - return true; - else - return false; -} -//!!!!!!!!!!!!!!!!!!!!!!! -void EsxModel::DataFilesModel::sort(int column, Qt::SortOrder order) -{ - emit layoutAboutToBeChanged(); - - if (column == 3) { - qSort(mFiles.begin(), mFiles.end(), lessThanDate); - } else { - qSort(mFiles.begin(), mFiles.end(), lessThanEsmFile); - } - - emit layoutChanged(); -} - -void EsxModel::DataFilesModel::addFile(const EsmFile *file) -{ - emit beginInsertRows(QModelIndex(), mFiles.count(), mFiles.count()); - mFiles.append(file); - emit endInsertRows(); -} - -void EsxModel::DataFilesModel::addFiles(const QString &path) -{ - QDir dir(path); - QStringList filters; - filters << "*.esp" << "*.esm" << "*.omwgame" << "*.omwaddon"; - dir.setNameFilters(filters); - - // Create a decoder for non-latin characters in esx metadata - QTextCodec *codec; - - if (mEncoding == QLatin1String("win1252")) { - codec = QTextCodec::codecForName("windows-1252"); - } else if (mEncoding == QLatin1String("win1251")) { - codec = QTextCodec::codecForName("windows-1251"); - } else if (mEncoding == QLatin1String("win1250")) { - codec = QTextCodec::codecForName("windows-1250"); - } else { - return; // This should never happen; - } - - QTextDecoder *decoder = codec->makeDecoder(); - - foreach (const QString &path, dir.entryList()) { - QFileInfo info(dir.absoluteFilePath(path)); - EsmFile *file = new EsmFile(path); - - try { - ESM::ESMReader fileReader; - ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(mEncoding.toStdString())); - fileReader.setEncoder(&encoder); - fileReader.open(dir.absoluteFilePath(path).toStdString()); - - std::vector mlist = fileReader.getMasters(); - - QStringList masters; - - for (unsigned int i = 0; i < mlist.size(); ++i) { - QString master = QString::fromStdString(mlist[i].name); - masters.append(master); - } - - file->setAuthor(decoder->toUnicode(fileReader.getAuthor().c_str())); - file->setSize(info.size()); - file->setDates(info.lastModified(), info.lastRead()); - file->setVersion(fileReader.getFVer()); - file->setPath(info.absoluteFilePath()); - file->setMasters(masters); - file->setDescription(decoder->toUnicode(fileReader.getDesc().c_str())); - - - // Put the file in the table - if (findItem(path) == 0) - addFile(file); - - } catch(std::runtime_error &e) { - // An error occurred while reading the .esp - qWarning() << "Error reading addon file: " << e.what(); - continue; - } - - } - - delete decoder; -} - -QModelIndex EsxModel::DataFilesModel::indexFromItem(const EsmFile *item) const -{ - if (item) - //return createIndex(mFiles.indexOf(item), 0); - return index(mFiles.indexOf(item),0); - - return QModelIndex(); -} - -EsxModel::EsmFileList EsxModel::DataFilesModel::checkedItems() -{ - EsmFileList list; - - EsmFileList::ConstIterator it; - EsmFileList::ConstIterator itEnd = mFiles.constEnd(); - - for (it = mFiles.constBegin(); it != itEnd; ++it) - { - // Only add the items that are in the checked list and available - if (mCheckStates[(*it)->fileName()] == Qt::Checked && canBeChecked(*it)) - list << (*it); - } - - return list; -} - -QStringList EsxModel::DataFilesModel::checkedItemsPaths() -{ - QStringList list; - - EsmFileList::ConstIterator it; - EsmFileList::ConstIterator itEnd = mFiles.constEnd(); - - int i = 0; - for (it = mFiles.constBegin(); it != itEnd; ++it) { - const EsmFile *file = item(i); - ++i; - - if (mCheckStates[file->fileName()] == Qt::Checked && canBeChecked(file)) - list << file->path(); - } - - return list; -} -void EsxModel::DataFilesModel::uncheckAll() -{ - emit layoutAboutToBeChanged(); - mCheckStates.clear(); - emit layoutChanged(); -} - -/* -EsxModel::EsmFileList EsxModel::DataFilesModel::uncheckedItems() -{ - EsmFileList list; - EsmFileList checked = checkedItems(); - - EsmFileList::ConstIterator it; - - for (it = mFiles.constBegin(); it != mFiles.constEnd(); ++it) - { - const EsmFile *file = *it; - - // Add the items that are not in the checked list - if (!checked.contains(file)) - list << file; - } - - return list; -} -*/ -bool EsxModel::DataFilesModel::canBeChecked(const EsmFile *file) const -{ - //element can be checked if all its dependencies are - foreach (const QString &master, file->masters()) - { - if (!mCheckStates.contains(master) || mCheckStates[master] != Qt::Checked) - return false; - } - return true; -} diff --git a/components/esxselector/model/datafilesmodel.hpp b/components/esxselector/model/datafilesmodel.hpp deleted file mode 100644 index 4f23cd5307..0000000000 --- a/components/esxselector/model/datafilesmodel.hpp +++ /dev/null @@ -1,80 +0,0 @@ -#ifndef DATAFILESMODEL_HPP -#define DATAFILESMODEL_HPP - -#include -#include -#include -#include - -namespace EsxModel -{ - class EsmFile; - - typedef QList EsmFileList; - - class DataFilesModel : public QAbstractTableModel - { - Q_OBJECT - - public: - explicit DataFilesModel(QObject *parent = 0); - virtual ~DataFilesModel(); - virtual int columnCount(const QModelIndex &parent = QModelIndex()) const; - virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; - bool removeRows(int row, int count, const QModelIndex &parent); - bool insertRows(int row, int count, const QModelIndex &parent); - - bool moveRow(int oldrow, int row, const QModelIndex &parent = QModelIndex()); - - virtual Qt::ItemFlags flags(const QModelIndex &index) const; - - virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; - - virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); - void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); - - inline QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const - { - QModelIndex idx = QAbstractTableModel::index(row, 0, parent); - return idx; - } - - void setEncoding(const QString &encoding); - - void addFiles(const QString &path); - - void uncheckAll(); - - Qt::DropActions supportedDropActions() const; - QStringList mimeTypes() const; - QMimeData *mimeData(const QModelIndexList &indexes) const; - bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); - - EsmFileList checkedItems(); - EsmFileList uncheckedItems(); - QStringList checkedItemsPaths(); - - Qt::CheckState checkState(const QModelIndex &index); - void setCheckState(const QModelIndex &index, Qt::CheckState state); - - QModelIndex indexFromItem(const EsmFile *item) const; - const EsmFile* findItem(const QString &name); - const EsmFile* item(int row) const; - - signals: - void checkedItemsChanged(const EsmFileList &items); - - private: - - bool canBeChecked(const EsmFile *file) const; - void addFile(const EsmFile *file); - - EsmFileList mFiles; - QHash mCheckStates; - - QString mEncoding; - - }; -} -#endif // DATAFILESMODEL_HPP diff --git a/components/esxselector/model/masterproxymodel.cpp b/components/esxselector/model/masterproxymodel.cpp deleted file mode 100644 index df74d03569..0000000000 --- a/components/esxselector/model/masterproxymodel.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include "masterproxymodel.hpp" -#include -#include - -EsxModel::MasterProxyModel::MasterProxyModel(QObject *parent, QAbstractTableModel* model) : - QSortFilterProxyModel(parent) -{ - setFilterRegExp(QString("game")); - setFilterRole (Qt::UserRole); - - if (model) - setSourceModel (model); -} diff --git a/components/esxselector/model/masterproxymodel.hpp b/components/esxselector/model/masterproxymodel.hpp deleted file mode 100644 index 8a5c730325..0000000000 --- a/components/esxselector/model/masterproxymodel.hpp +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef MASTERPROXYMODEL_HPP -#define MASTERPROXYMODEL_HPP - -#include -#include -#include - -class QAbstractTableModel; - -namespace EsxModel -{ - class MasterProxyModel : public QSortFilterProxyModel - { - Q_OBJECT - public: - explicit MasterProxyModel(QObject *parent = 0, QAbstractTableModel *model = 0); - // virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; - - signals: - - public slots: - void slotSourceModelChanged(QModelIndex topLeft, QModelIndex botRight); - }; -} -#endif // MASTERPROXYMODEL_HPP diff --git a/components/esxselector/model/modelitem.cpp b/components/esxselector/model/modelitem.cpp deleted file mode 100644 index 03b19f6910..0000000000 --- a/components/esxselector/model/modelitem.cpp +++ /dev/null @@ -1,69 +0,0 @@ -#include "modelitem.hpp" - -EsxModel::ModelItem::ModelItem(ModelItem *parent) - : mParentItem(parent) -{ -} -/* -EsxModel::ModelItem::ModelItem(const ModelItem *parent) - // : mParentItem(parent) -{ -} -*/ - -EsxModel::ModelItem::~ModelItem() -{ - qDeleteAll(mChildItems); -} - - -EsxModel::ModelItem *EsxModel::ModelItem::parent() const -{ - return mParentItem; -} - -bool EsxModel::ModelItem::hasFormat(const QString &mimetype) const -{ - if (mimetype == "application/omwcontent") - return true; - - return QMimeData::hasFormat(mimetype); -} -int EsxModel::ModelItem::row() const -{ - if (mParentItem) - return 1; - //return mParentItem->childRow(const_cast(this)); - //return mParentItem->mChildItems.indexOf(const_cast(this)); - - return -1; -} - - -int EsxModel::ModelItem::childCount() const -{ - return mChildItems.count(); -} - -int EsxModel::ModelItem::childRow(ModelItem *child) const -{ - Q_ASSERT(child); - - return mChildItems.indexOf(child); -} - -EsxModel::ModelItem *EsxModel::ModelItem::child(int row) -{ - return mChildItems.value(row); -} - - -void EsxModel::ModelItem::appendChild(ModelItem *item) -{ - mChildItems.append(item); -} - -void EsxModel::ModelItem::removeChild(int row) -{ - mChildItems.removeAt(row); -} diff --git a/components/esxselector/model/pluginsproxymodel.cpp b/components/esxselector/model/pluginsproxymodel.cpp deleted file mode 100644 index c543672b09..0000000000 --- a/components/esxselector/model/pluginsproxymodel.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include "pluginsproxymodel.hpp" -#include "contentmodel.hpp" -#include - -EsxModel::PluginsProxyModel::PluginsProxyModel(QObject *parent, ContentModel *model) : - QSortFilterProxyModel(parent) -{ - setFilterRegExp (QString("addon")); - setFilterRole (Qt::UserRole); - setDynamicSortFilter (true); - - if (model) - setSourceModel (model); -} diff --git a/components/esxselector/model/pluginsproxymodel.hpp b/components/esxselector/model/pluginsproxymodel.hpp deleted file mode 100644 index 4415df716e..0000000000 --- a/components/esxselector/model/pluginsproxymodel.hpp +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef PLUGINSPROXYMODEL_HPP -#define PLUGINSPROXYMODEL_HPP - -#include - -class QVariant; -class QAbstractTableModel; - -namespace EsxModel -{ - class ContentModel; - - class PluginsProxyModel : public QSortFilterProxyModel - { - Q_OBJECT - - public: - - explicit PluginsProxyModel(QObject *parent = 0, ContentModel *model = 0); - ~PluginsProxyModel(); - - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; - - bool removeRows(int row, int count, const QModelIndex &parent); - }; -} - -#endif // PLUGINSPROXYMODEL_HPP diff --git a/components/esxselector/model/sourcemodel.cpp b/components/esxselector/model/sourcemodel.cpp deleted file mode 100644 index 7b54adba91..0000000000 --- a/components/esxselector/model/sourcemodel.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include "sourcemodel.h" - -SourceModel::SourceModel(QObject *parent) : - QAbstractTableClass(parent) -{ -} diff --git a/components/esxselector/model/sourcemodel.h b/components/esxselector/model/sourcemodel.h deleted file mode 100644 index cf51456755..0000000000 --- a/components/esxselector/model/sourcemodel.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef SOURCEMODEL_H -#define SOURCEMODEL_H - -#include - -class SourceModel : public QAbstractTableClass -{ - Q_OBJECT -public: - explicit SourceModel(QObject *parent = 0); - -signals: - -public slots: - -}; - -#endif // SOURCEMODEL_H diff --git a/components/esxselector/view/contentselector.cpp b/components/esxselector/view/contentselector.cpp deleted file mode 100644 index 4f8ac7253a..0000000000 --- a/components/esxselector/view/contentselector.cpp +++ /dev/null @@ -1,148 +0,0 @@ -#include "contentselector.hpp" - -#include "../model/datafilesmodel.hpp" -#include "../model/contentmodel.hpp" -#include "../model/esmfile.hpp" - -#include - -#include -#include -#include - -EsxView::ContentSelector::ContentSelector(QWidget *parent) : - QDialog(parent) -{ - setupUi(this); - - buildSourceModel(); - buildMasterView(); - buildPluginsView(); - buildProfilesView(); - - updateViews(); - -} - -void EsxView::ContentSelector::buildSourceModel() -{ - mContentModel = new EsxModel::ContentModel(); - connect(mContentModel, SIGNAL(layoutChanged()), this, SLOT(updateViews())); -} - -void EsxView::ContentSelector::buildMasterView() -{ - mMasterProxyModel = new QSortFilterProxyModel(this); - mMasterProxyModel->setFilterRegExp(QString("game")); - mMasterProxyModel->setFilterRole (Qt::UserRole); - mMasterProxyModel->setSourceModel (mContentModel); - - masterView->setPlaceholderText(QString("Select a game file...")); - masterView->setModel(mMasterProxyModel); - - connect(masterView, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCurrentMasterIndexChanged(int))); - - masterView->setCurrentIndex(-1); - masterView->setCurrentIndex(0); -} - -void EsxView::ContentSelector::buildPluginsView() -{ - mPluginsProxyModel = new QSortFilterProxyModel(this); - mPluginsProxyModel->setFilterRegExp (QString("addon")); - mPluginsProxyModel->setFilterRole (Qt::UserRole); - mPluginsProxyModel->setDynamicSortFilter (true); - mPluginsProxyModel->setSourceModel (mContentModel); - - pluginView->setModel(mPluginsProxyModel); - - connect(pluginView, SIGNAL(clicked(const QModelIndex &)), this, SLOT(slotPluginTableItemClicked(const QModelIndex &))); -} - -void EsxView::ContentSelector::buildProfilesView() -{ - profilesComboBox->setPlaceholderText(QString("Select a profile...")); - connect(profilesComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCurrentProfileIndexChanged(int))); -} - -void EsxView::ContentSelector::updateViews() -{ - // Ensure the columns are hidden because sort() re-enables them - pluginView->setColumnHidden(1, true); - pluginView->setColumnHidden(2, true); - pluginView->setColumnHidden(3, true); - pluginView->setColumnHidden(4, true); - pluginView->setColumnHidden(5, true); - pluginView->setColumnHidden(6, true); - pluginView->setColumnHidden(7, true); - pluginView->setColumnHidden(8, true); - pluginView->resizeColumnsToContents(); -} - -void EsxView::ContentSelector::addFiles(const QString &path) -{ - mContentModel->addFiles(path); - mContentModel->sort(3); // Sort by date accessed - masterView->setCurrentIndex(-1); - mContentModel->uncheckAll(); -} - -void EsxView::ContentSelector::setEncoding(const QString &encoding) -{ - mContentModel->setEncoding(encoding); -} - -QStringList EsxView::ContentSelector::checkedItemsPaths() -{ - QStringList itemPaths; - - foreach( const EsxModel::EsmFile *file, mContentModel->checkedItems()) - itemPaths << file->path(); - - return itemPaths; -} - -void EsxView::ContentSelector::slotCurrentProfileIndexChanged(int index) -{ - emit profileChanged(index); -} - -void EsxView::ContentSelector::slotCurrentMasterIndexChanged(int index) -{ - static int oldIndex = -1; - - QAbstractItemModel *const model = masterView->model(); - QSortFilterProxyModel *proxy = dynamic_cast(model); - - if (proxy) - proxy->setDynamicSortFilter(false); - - if (oldIndex > -1) - qDebug() << "clearing old master check state"; - model->setData(model->index(oldIndex, 0), false, Qt::UserRole + 1); - - oldIndex = index; - - qDebug() << "setting new master check state"; - model->setData(model->index(index, 0), true, Qt::UserRole + 1); - - if (proxy) - proxy->setDynamicSortFilter(true); -} - -void EsxView::ContentSelector::slotPluginTableItemClicked(const QModelIndex &index) -{ - QAbstractItemModel *const model = pluginView->model(); - QSortFilterProxyModel *proxy = dynamic_cast(model); - - if (proxy) - proxy->setDynamicSortFilter(false); - - if (model->data(index, Qt::CheckStateRole).toInt() == Qt::Unchecked) - model->setData(index, Qt::Checked, Qt::CheckStateRole); - else - model->setData(index, Qt::Unchecked, Qt::CheckStateRole); - - if (proxy) - proxy->setDynamicSortFilter(true); -} diff --git a/files/ui/datafilespage.ui b/files/ui/datafilespage.ui index 5b0a6d2292..82d00922b0 100644 --- a/files/ui/datafilespage.ui +++ b/files/ui/datafilespage.ui @@ -23,7 +23,7 @@ - + false @@ -34,7 +34,7 @@ - + 0 @@ -102,7 +102,7 @@ - + Enter project name... @@ -159,7 +159,7 @@ 6
- + true @@ -251,12 +251,12 @@ EsxView::ProfilesComboBox QComboBox -

components/esxselector/view/profilescombobox.hpp
+
components/contentselector/view/profilescombobox.hpp
EsxView::LineEdit QLineEdit -
components/esxselector/view/lineedit.hpp
+
components/contentselector/view/lineedit.hpp
From 513f0c4b3ef1d15160619b49eb95d6a23a411c59 Mon Sep 17 00:00:00 2001 From: graffy76 Date: Sun, 22 Sep 2013 23:52:53 -0500 Subject: [PATCH 045/434] Implemented file/adjuster widgets into new addon creation dialog --- apps/launcher/datafilespage.cpp | 4 +- apps/opencs/view/doc/adjusterwidget.cpp | 6 +- apps/opencs/view/doc/adjusterwidget.hpp | 5 +- apps/opencs/view/doc/filedialog.cpp | 53 ++++++++++-- apps/opencs/view/doc/filedialog.hpp | 17 +++- apps/opencs/view/doc/filewidget.cpp | 7 +- apps/opencs/view/doc/filewidget.hpp | 2 + .../contentselector/view/contentselector.cpp | 5 +- .../contentselector/view/contentselector.hpp | 1 + files/ui/datafilespage.ui | 80 ++++++++++++++++--- 10 files changed, 152 insertions(+), 28 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 1526b705ae..44392794bd 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -306,7 +306,7 @@ void DataFilesPage::setPluginsCheckstates(Qt::CheckState state) if (!sourceIndex.isValid()) return; - bool isChecked = ( state == Qt::Checked ); + //bool isChecked = ( state == Qt::Checked ); mContentModel->setData(sourceIndex, state, Qt::CheckStateRole); } @@ -397,7 +397,7 @@ void DataFilesPage::slotCurrentGameFileIndexChanged(int index) void DataFilesPage::slotAddonTableItemClicked(const QModelIndex &index) { QAbstractItemModel *const model = addonView->model(); - QSortFilterProxyModel *proxy = dynamic_cast(model); + //QSortFilterProxyModel *proxy = dynamic_cast(model); if (model->data(index, Qt::CheckStateRole).toInt() == Qt::Unchecked) model->setData(index, Qt::Checked, Qt::CheckStateRole); diff --git a/apps/opencs/view/doc/adjusterwidget.cpp b/apps/opencs/view/doc/adjusterwidget.cpp index 9108197006..2784bca8c4 100644 --- a/apps/opencs/view/doc/adjusterwidget.cpp +++ b/apps/opencs/view/doc/adjusterwidget.cpp @@ -43,6 +43,10 @@ boost::filesystem::path CSVDoc::AdjusterWidget::getPath() const return mResultPath; } +bool CSVDoc::AdjusterWidget::isValid() const +{ + return mValid; +} void CSVDoc::AdjusterWidget::setName (const QString& name, bool addon) { QString message; @@ -88,4 +92,4 @@ void CSVDoc::AdjusterWidget::setName (const QString& name, bool addon) pixmap (QSize (16, 16))); emit stateChanged (mValid); -} \ No newline at end of file +} diff --git a/apps/opencs/view/doc/adjusterwidget.hpp b/apps/opencs/view/doc/adjusterwidget.hpp index f578dc4aeb..d970cffee6 100644 --- a/apps/opencs/view/doc/adjusterwidget.hpp +++ b/apps/opencs/view/doc/adjusterwidget.hpp @@ -25,6 +25,9 @@ namespace CSVDoc void setLocalData (const boost::filesystem::path& localData); + QString getText() const; + bool isValid() const; + boost::filesystem::path getPath() const; ///< This function must not be called if there is no valid path. @@ -38,4 +41,4 @@ namespace CSVDoc }; } -#endif \ No newline at end of file +#endif diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index 1d6bed7a7c..9dce090a1e 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -9,12 +9,22 @@ #include #include #include +#include #include #include +#include "filewidget.hpp" +#include "adjusterwidget.hpp" + +#include + CSVDoc::FileDialog::FileDialog(QWidget *parent) : - ContentSelector(parent) + ContentSelector(parent), + mFileWidget (new FileWidget (this)), + mAdjusterWidget (new AdjusterWidget (this)), + mEnable_1(false), + mEnable_2(false) { // Hide the profile elements profileGroupBox->hide(); @@ -22,12 +32,20 @@ CSVDoc::FileDialog::FileDialog(QWidget *parent) : resize(400, 400); - connect(projectNameLineEdit, SIGNAL(textChanged(QString)), this, SLOT(updateCreateButton(QString))); + mFileWidget->setType(true); + mFileWidget->extensionLabelIsVisible(false); connect(projectCreateButton, SIGNAL(clicked()), this, SIGNAL(createNewFile())); connect(projectButtonBox, SIGNAL(accepted()), this, SIGNAL(openFiles())); connect(projectButtonBox, SIGNAL(rejected()), this, SLOT(reject())); + + connect (mFileWidget, SIGNAL (nameChanged (const QString&, bool)), + mAdjusterWidget, SLOT (setName (const QString&, bool))); + + connect (mAdjusterWidget, SIGNAL (stateChanged (bool)), this, SLOT (slotAdjusterChanged(bool))); + connect (this, SIGNAL (signalGameFileChanged(int)), this, SLOT (slotGameFileSelected(int))); + connect (this, SIGNAL (signalUpdateCreateButton(bool, int)), this, SLOT (slotEnableCreateButton(bool, int))); } void CSVDoc::FileDialog::updateOpenButton(const QStringList &items) @@ -40,24 +58,30 @@ void CSVDoc::FileDialog::updateOpenButton(const QStringList &items) openButton->setEnabled(!items.isEmpty()); } -void CSVDoc::FileDialog::updateCreateButton(const QString &name) +void CSVDoc::FileDialog::slotEnableCreateButton(bool enable, int widgetNumber) { - if (!projectCreateButton->isVisible()) - return; - projectCreateButton->setEnabled(!name.isEmpty()); + if (widgetNumber == 1) + mEnable_1 = enable; + + if (widgetNumber == 2) + mEnable_2 = enable; + + qDebug() << "update enabled" << mEnable_1 << mEnable_2 << enable; + projectCreateButton->setEnabled(mEnable_1 && mEnable_2); } QString CSVDoc::FileDialog::fileName() { - return projectNameLineEdit->text(); + return mFileWidget->getName(); } void CSVDoc::FileDialog::openFile() { setWindowTitle(tr("Open")); - projectNameLineEdit->hide(); + mFileWidget->hide(); + adjusterWidgetFrame->hide(); projectCreateButton->hide(); projectGroupBox->setTitle(tr("")); projectButtonBox->button(QDialogButtonBox::Open)->setEnabled(false); @@ -71,6 +95,9 @@ void CSVDoc::FileDialog::newFile() { setWindowTitle(tr("New")); + fileWidgetFrame->layout()->addWidget(mFileWidget); + adjusterWidgetFrame->layout()->addWidget(mAdjusterWidget); + projectButtonBox->setStandardButtons(QDialogButtonBox::Cancel); projectButtonBox->addButton(projectCreateButton, QDialogButtonBox::ActionRole); @@ -78,3 +105,13 @@ void CSVDoc::FileDialog::newFile() raise(); activateWindow(); } + +void CSVDoc::FileDialog::slotAdjusterChanged(bool value) +{ + emit signalUpdateCreateButton(mAdjusterWidget->isValid(), 2); +} + +void CSVDoc::FileDialog::slotGameFileSelected(int value) +{ + emit signalUpdateCreateButton(value > -1, 1); +} diff --git a/apps/opencs/view/doc/filedialog.hpp b/apps/opencs/view/doc/filedialog.hpp index d0c3461b9f..7782dd94ea 100644 --- a/apps/opencs/view/doc/filedialog.hpp +++ b/apps/opencs/view/doc/filedialog.hpp @@ -26,9 +26,19 @@ namespace ContentSelectorView namespace CSVDoc { + class FileWidget; + class AdjusterWidget; + class FileDialog : public ContentSelectorView::ContentSelector { Q_OBJECT + + FileWidget *mFileWidget; + AdjusterWidget *mAdjusterWidget; + + bool mEnable_1; + bool mEnable_2; + public: explicit FileDialog(QWidget *parent = 0); @@ -41,12 +51,17 @@ namespace CSVDoc void openFiles(); void createNewFile(); + void signalUpdateCreateButton (bool, int); + void signalUpdateCreateButtonFlags(int); + public slots: private slots: //void updateViews(); void updateOpenButton(const QStringList &items); - void updateCreateButton(const QString &name); + void slotEnableCreateButton(bool enable, int widgetNumber); + void slotAdjusterChanged(bool value); + void slotGameFileSelected(int value); }; } #endif // FILEDIALOG_HPP diff --git a/apps/opencs/view/doc/filewidget.cpp b/apps/opencs/view/doc/filewidget.cpp index c8f33e92d5..9cd2fad422 100644 --- a/apps/opencs/view/doc/filewidget.cpp +++ b/apps/opencs/view/doc/filewidget.cpp @@ -50,4 +50,9 @@ QString CSVDoc::FileWidget::getName() const void CSVDoc::FileWidget::textChanged (const QString& text) { emit nameChanged (getName(), mAddon); -} \ No newline at end of file +} + +void CSVDoc::FileWidget::extensionLabelIsVisible(bool visible) +{ + mType->setVisible(visible); +} diff --git a/apps/opencs/view/doc/filewidget.hpp b/apps/opencs/view/doc/filewidget.hpp index c51c29632e..ff09d71a39 100644 --- a/apps/opencs/view/doc/filewidget.hpp +++ b/apps/opencs/view/doc/filewidget.hpp @@ -27,6 +27,8 @@ namespace CSVDoc QString getName() const; + void extensionLabelIsVisible(bool visible); + private slots: void textChanged (const QString& text); diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index 615e9a846d..e6ed0ec567 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -40,6 +40,7 @@ void ContentSelectorView::ContentSelector::buildGameFileView() gameFileView->setModel(mGameFileProxyModel); connect(gameFileView, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCurrentGameFileIndexChanged(int))); + connect(gameFileView, SIGNAL(currentIndexChanged(int)), this, SIGNAL(signalGameFileChanged(int))); gameFileView->setCurrentIndex(-1); gameFileView->setCurrentIndex(0); @@ -120,12 +121,14 @@ void ContentSelectorView::ContentSelector::slotCurrentGameFileIndexChanged(int i if (proxy) proxy->setDynamicSortFilter(true); + + emit signalGameFileChanged(true); } void ContentSelectorView::ContentSelector::slotAddonTableItemClicked(const QModelIndex &index) { QAbstractItemModel *const model = addonView->model(); - QSortFilterProxyModel *proxy = dynamic_cast(model); + //QSortFilterProxyModel *proxy = dynamic_cast(model); if (model->data(index, Qt::CheckStateRole).toInt() == Qt::Unchecked) model->setData(index, Qt::Checked, Qt::CheckStateRole); diff --git a/components/contentselector/view/contentselector.hpp b/components/contentselector/view/contentselector.hpp index 8032b04495..5af53dc464 100644 --- a/components/contentselector/view/contentselector.hpp +++ b/components/contentselector/view/contentselector.hpp @@ -40,6 +40,7 @@ namespace ContentSelectorView signals: void profileChanged(int index); + void signalGameFileChanged(int value); private slots: void updateViews(); diff --git a/files/ui/datafilespage.ui b/files/ui/datafilespage.ui index 82d00922b0..9494077591 100644 --- a/files/ui/datafilespage.ui +++ b/files/ui/datafilespage.ui @@ -10,6 +10,12 @@ 313 + + + 0 + 0 + + Qt::DefaultContextMenu @@ -97,15 +103,44 @@
+ + + 0 + 0 + + Project + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + - - - Enter project name... + + + + 0 + 0 + + + QFrame::NoFrame + + + QFrame::Plain + + + 0 + + + + 0 + + + 0 + + @@ -126,9 +161,33 @@
- projectButtonBox - projectCreateButton - projectNameLineEdit +
+
+ + + + + 1 + 0 + + + + QFrame::NoFrame + + + QFrame::Plain + + + 0 + + + + 0 + + + 0 + + @@ -249,14 +308,9 @@
- EsxView::ProfilesComboBox + ContentSelectorView::ProfilesComboBox QComboBox -
components/contentselector/view/profilescombobox.hpp
-
- - EsxView::LineEdit - QLineEdit -
components/contentselector/view/lineedit.hpp
+
components/contentselector/view/profilescombobox.hpp
From 63e0cf5154572f90b0c4234a64703d6056464123 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 23 Sep 2013 11:26:29 +0200 Subject: [PATCH 046/434] fixed missing initialisation of adjuster widget in file dialogue --- apps/opencs/editor.cpp | 1 + apps/opencs/view/doc/filedialog.cpp | 5 +++++ apps/opencs/view/doc/filedialog.hpp | 9 +++++++++ apps/opencs/view/doc/newgame.hpp | 3 +++ 4 files changed, 18 insertions(+) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index ba1dfb57ec..104afa03b5 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -17,6 +17,7 @@ CS::Editor::Editor() : mViewManager (mDocumentManager) setupDataFiles(); mNewGame.setLocalData (mLocal); + mFileDialog.setLocalData (mLocal); connect (&mViewManager, SIGNAL (newGameRequest ()), this, SLOT (createGame ())); connect (&mViewManager, SIGNAL (newAddonRequest ()), this, SLOT (createAddon ())); diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index 9dce090a1e..4d7c5bc159 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -48,6 +48,11 @@ CSVDoc::FileDialog::FileDialog(QWidget *parent) : connect (this, SIGNAL (signalUpdateCreateButton(bool, int)), this, SLOT (slotEnableCreateButton(bool, int))); } +void CSVDoc::FileDialog::setLocalData (const boost::filesystem::path& localData) +{ + mAdjusterWidget->setLocalData (localData); +} + void CSVDoc::FileDialog::updateOpenButton(const QStringList &items) { QPushButton *openButton = projectButtonBox->button(QDialogButtonBox::Open); diff --git a/apps/opencs/view/doc/filedialog.hpp b/apps/opencs/view/doc/filedialog.hpp index 7782dd94ea..0c914b932a 100644 --- a/apps/opencs/view/doc/filedialog.hpp +++ b/apps/opencs/view/doc/filedialog.hpp @@ -4,9 +4,16 @@ #include #include +#include + #include "components/contentselector/view/contentselector.hpp" #include "ui_datafilespage.h" +#ifndef CS_QT_BOOST_FILESYSTEM_PATH_DECLARED +#define CS_QT_BOOST_FILESYSTEM_PATH_DECLARED +Q_DECLARE_METATYPE (boost::filesystem::path) +#endif + class QDialogButtonBox; class QSortFilterProxyModel; class QAbstractItemModel; @@ -42,6 +49,8 @@ namespace CSVDoc public: explicit FileDialog(QWidget *parent = 0); + void setLocalData (const boost::filesystem::path& localData); + void openFile(); void newFile(); diff --git a/apps/opencs/view/doc/newgame.hpp b/apps/opencs/view/doc/newgame.hpp index aa97682ff6..9ad7ea1690 100644 --- a/apps/opencs/view/doc/newgame.hpp +++ b/apps/opencs/view/doc/newgame.hpp @@ -6,7 +6,10 @@ #include #include +#ifndef CS_QT_BOOST_FILESYSTEM_PATH_DECLARED +#define CS_QT_BOOST_FILESYSTEM_PATH_DECLARED Q_DECLARE_METATYPE (boost::filesystem::path) +#endif class QPushButton; From 74d683b5303e5b95a34fec6d082b9999aacecc3b Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 23 Sep 2013 11:58:11 +0200 Subject: [PATCH 047/434] fixed save path for newly created addons --- apps/opencs/editor.cpp | 9 ++++----- apps/opencs/editor.hpp | 2 +- apps/opencs/view/doc/filedialog.cpp | 9 +++++++-- apps/opencs/view/doc/filedialog.hpp | 3 ++- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 104afa03b5..800f3984e9 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -30,7 +30,8 @@ CS::Editor::Editor() : mViewManager (mDocumentManager) connect (&mStartup, SIGNAL (editConfig()), this, SLOT (showSettings ())); connect (&mFileDialog, SIGNAL(openFiles()), this, SLOT(openFiles())); - connect (&mFileDialog, SIGNAL(createNewFile()), this, SLOT(createNewFile())); + connect (&mFileDialog, SIGNAL(createNewFile (const boost::filesystem::path&)), + this, SLOT(createNewFile (const boost::filesystem::path&))); connect (&mNewGame, SIGNAL (createRequest (const boost::filesystem::path&)), this, SLOT (createNewGame (const boost::filesystem::path&))); @@ -138,7 +139,7 @@ void CS::Editor::openFiles() mFileDialog.hide(); } -void CS::Editor::createNewFile() +void CS::Editor::createNewFile (const boost::filesystem::path& savePath) { std::vector files; QStringList paths = mFileDialog.checkedItemsPaths(); @@ -149,9 +150,7 @@ void CS::Editor::createNewFile() files.push_back(mFileDialog.fileName().toStdString()); - /// \todo Get the save path from the file dialogue. - - CSMDoc::Document *document = mDocumentManager.addDocument (files, *files.rbegin(), true); + CSMDoc::Document *document = mDocumentManager.addDocument (files, savePath, true); mViewManager.addView (document); mFileDialog.hide(); diff --git a/apps/opencs/editor.hpp b/apps/opencs/editor.hpp index 248ebf2c54..abf9496e4b 100644 --- a/apps/opencs/editor.hpp +++ b/apps/opencs/editor.hpp @@ -60,7 +60,7 @@ namespace CS void loadDocument(); void openFiles(); - void createNewFile(); + void createNewFile (const boost::filesystem::path& savePath); void createNewGame (const boost::filesystem::path& file); void showStartup(); diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index 4d7c5bc159..4426d2e49c 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -35,7 +35,7 @@ CSVDoc::FileDialog::FileDialog(QWidget *parent) : mFileWidget->setType(true); mFileWidget->extensionLabelIsVisible(false); - connect(projectCreateButton, SIGNAL(clicked()), this, SIGNAL(createNewFile())); + connect(projectCreateButton, SIGNAL(clicked()), this, SLOT(createNewFile())); connect(projectButtonBox, SIGNAL(accepted()), this, SIGNAL(openFiles())); connect(projectButtonBox, SIGNAL(rejected()), this, SLOT(reject())); @@ -118,5 +118,10 @@ void CSVDoc::FileDialog::slotAdjusterChanged(bool value) void CSVDoc::FileDialog::slotGameFileSelected(int value) { - emit signalUpdateCreateButton(value > -1, 1); + emit signalUpdateCreateButton(value > -1, 1); } + +void CSVDoc::FileDialog::createNewFile() +{ + emit createNewFile (mAdjusterWidget->getPath()); +} \ No newline at end of file diff --git a/apps/opencs/view/doc/filedialog.hpp b/apps/opencs/view/doc/filedialog.hpp index 0c914b932a..37119ff0d0 100644 --- a/apps/opencs/view/doc/filedialog.hpp +++ b/apps/opencs/view/doc/filedialog.hpp @@ -58,7 +58,7 @@ namespace CSVDoc signals: void openFiles(); - void createNewFile(); + void createNewFile (const boost::filesystem::path& savePath); void signalUpdateCreateButton (bool, int); void signalUpdateCreateButtonFlags(int); @@ -71,6 +71,7 @@ namespace CSVDoc void slotEnableCreateButton(bool enable, int widgetNumber); void slotAdjusterChanged(bool value); void slotGameFileSelected(int value); + void createNewFile(); }; } #endif // FILEDIALOG_HPP From 6d9ff39390f5495535c2b18c2664554836cf83aa Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 23 Sep 2013 12:16:56 +0200 Subject: [PATCH 048/434] set dependencies when saving (requires further refinements) --- apps/opencs/model/doc/document.cpp | 7 ++++++- apps/opencs/model/doc/document.hpp | 5 +++++ apps/opencs/model/doc/savingstages.cpp | 13 ++++++++++++- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index 525f18a206..37294bcd16 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -2141,7 +2141,7 @@ void CSMDoc::Document::createBase() CSMDoc::Document::Document (const std::vector& files, const boost::filesystem::path& savePath, bool new_) -: mSavePath (savePath), mTools (mData), mSaving (*this) +: mSavePath (savePath), mContentFiles (files), mTools (mData), mSaving (*this) { if (files.empty()) throw std::runtime_error ("Empty content file sequence"); @@ -2200,6 +2200,11 @@ const boost::filesystem::path& CSMDoc::Document::getSavePath() const return mSavePath; } +const std::vector& CSMDoc::Document::getContentFiles() const +{ + return mContentFiles; +} + void CSMDoc::Document::save() { if (mSaving.isRunning()) diff --git a/apps/opencs/model/doc/document.hpp b/apps/opencs/model/doc/document.hpp index 5a03955105..b1c6a02731 100644 --- a/apps/opencs/model/doc/document.hpp +++ b/apps/opencs/model/doc/document.hpp @@ -33,6 +33,7 @@ namespace CSMDoc private: boost::filesystem::path mSavePath; + std::vector mContentFiles; CSMWorld::Data mData; CSMTools::Tools mTools; Saving mSaving; @@ -74,6 +75,10 @@ namespace CSMDoc const boost::filesystem::path& getSavePath() const; + const std::vector& getContentFiles() const; + ///< \attention The last element in this collection is the file that is being edited, + /// but with its original path instead of the save path. + void save(); CSMWorld::UniversalId verify(); diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index 797b32eae8..fd62345940 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -50,7 +50,18 @@ void CSMDoc::WriteHeaderStage::perform (int stage, std::vector& mes mState.getWriter().setDescription (""); mState.getWriter().setRecordCount (0); - /// \todo fill in dependency list + /// \todo refine dependency list (at least remove redundant dependencies) + std::vector dependencies = mDocument.getContentFiles(); + std::vector::const_iterator end (--dependencies.end()); + + for (std::vector::const_iterator iter (dependencies.begin()); + iter!=end; ++iter) + { + std::string name = iter->filename().string(); + uint64_t size = boost::filesystem::file_size (*iter); + + mState.getWriter().addMaster (name, size); + } mState.getWriter().save (mState.getStream()); } From d7cff6361e091990e7b3a90fc94f824284543c13 Mon Sep 17 00:00:00 2001 From: graffy76 Date: Mon, 23 Sep 2013 06:51:49 -0500 Subject: [PATCH 049/434] Fixed filter issue (all addons for a gamefile are enabled for checking). Note: Other dependencies are not yet automatically selected when an addon is checked. --- apps/opencs/view/doc/adjusterwidget.hpp | 1 - apps/opencs/view/doc/filedialog.cpp | 3 +- .../contentselector/model/contentmodel.cpp | 32 +++++++++++++------ .../contentselector/model/contentmodel.hpp | 3 +- 4 files changed, 26 insertions(+), 13 deletions(-) diff --git a/apps/opencs/view/doc/adjusterwidget.hpp b/apps/opencs/view/doc/adjusterwidget.hpp index d970cffee6..461cfb3452 100644 --- a/apps/opencs/view/doc/adjusterwidget.hpp +++ b/apps/opencs/view/doc/adjusterwidget.hpp @@ -25,7 +25,6 @@ namespace CSVDoc void setLocalData (const boost::filesystem::path& localData); - QString getText() const; bool isValid() const; boost::filesystem::path getPath() const; diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index 9dce090a1e..68aab27d5f 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -67,7 +67,6 @@ void CSVDoc::FileDialog::slotEnableCreateButton(bool enable, int widgetNumber) if (widgetNumber == 2) mEnable_2 = enable; - qDebug() << "update enabled" << mEnable_1 << mEnable_2 << enable; projectCreateButton->setEnabled(mEnable_1 && mEnable_2); } @@ -113,5 +112,5 @@ void CSVDoc::FileDialog::slotAdjusterChanged(bool value) void CSVDoc::FileDialog::slotGameFileSelected(int value) { - emit signalUpdateCreateButton(value > -1, 1); + emit signalUpdateCreateButton(value > -1, 1); } diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index b85da25c67..c1394ef47d 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -65,7 +65,7 @@ ContentSelectorModel::EsmFile *ContentSelectorModel::ContentModel::item(int row) return 0; } -const ContentSelectorModel::EsmFile *ContentSelectorModel::ContentModel::findItem(const QString &name) const +const ContentSelectorModel::EsmFile *ContentSelectorModel::ContentModel::item(const QString &name) const { foreach (const EsmFile *file, mFiles) { @@ -99,7 +99,7 @@ Qt::ItemFlags ContentSelectorModel::ContentModel::flags(const QModelIndex &index if (canBeChecked(file)) return Qt::ItemIsEnabled | mDragDropFlags | mDefaultFlags; - return mDefaultFlags; + return Qt::NoItemFlags; } QVariant ContentSelectorModel::ContentModel::data(const QModelIndex &index, int role) const @@ -167,7 +167,7 @@ QVariant ContentSelectorModel::ContentModel::data(const QModelIndex &index, int if (file->isGameFile()) return ContentType_GameFile; else - if (flags(index) & Qt::ItemIsEnabled) + if (flags(index) & mDefaultFlags) return ContentType_Addon; break; @@ -373,12 +373,26 @@ bool ContentSelectorModel::ContentModel::dropMimeData(const QMimeData *data, Qt: bool ContentSelectorModel::ContentModel::canBeChecked(const EsmFile *file) const { - //element can be checked if all its dependencies are - foreach (const QString &gamefile, file->gameFiles()) - if (!isChecked(gamefile)) - return false; + //game files can always be checked + if (file->isGameFile()) + return true; - return true; + //addon can be checked if its gamefile is + foreach (const QString &fileName, file->gameFiles()) + { + const EsmFile *dependency = item(fileName); + + if (!dependency) + continue; + + if (dependency->isGameFile()) + { + if (isChecked(fileName)) + return true; + } + } + + return false; } void ContentSelectorModel::ContentModel::addFile(EsmFile *file) @@ -422,7 +436,7 @@ void ContentSelectorModel::ContentModel::addFiles(const QString &path) // Put the file in the table - if (findItem(path) == 0) + if (item(path) == 0) addFile(file); } catch(std::runtime_error &e) { diff --git a/components/contentselector/model/contentmodel.hpp b/components/contentselector/model/contentmodel.hpp index a8ff103da7..a2a57f850a 100644 --- a/components/contentselector/model/contentmodel.hpp +++ b/components/contentselector/model/contentmodel.hpp @@ -42,7 +42,7 @@ namespace ContentSelectorModel void addFiles(const QString &path); QModelIndex indexFromItem(const EsmFile *item) const; - const EsmFile *findItem(const QString &name) const; + const EsmFile *item(const QString &name) const; bool isChecked(const QString &name) const; void setCheckState(const QString &name, bool isChecked); @@ -54,6 +54,7 @@ namespace ContentSelectorModel void addFile(EsmFile *file); const EsmFile *item(int row) const; EsmFile *item(int row); + bool canBeChecked(const EsmFile *file) const; ContentFileList mFiles; From 9d358dd44c4df478ac90650338809321aa837aa8 Mon Sep 17 00:00:00 2001 From: graffy76 Date: Mon, 23 Sep 2013 22:01:44 -0500 Subject: [PATCH 050/434] Further implemented auto-checking / unchecking of dependencies --- .../contentselector/model/contentmodel.cpp | 38 ++++++++++++++++++- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index c1394ef47d..db6431810d 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -458,17 +458,51 @@ bool ContentSelectorModel::ContentModel::isChecked(const QString& name) const return false; } -void ContentSelectorModel::ContentModel::setCheckState(const QString &name, bool isChecked) +void ContentSelectorModel::ContentModel::setCheckState(const QString &name, bool checkState) { if (name.isEmpty()) return; Qt::CheckState state = Qt::Unchecked; - if (isChecked) + if (checkState) state = Qt::Checked; mCheckStates[name] = state; + + const EsmFile *file = item(name); + + if (state == Qt::Checked) + { + foreach (const QString &upstreamName, file->gameFiles()) + { + const EsmFile *upstreamFile = item(upstreamName); + + if (!upstreamFile) + continue; + + if (!isChecked(upstreamName)) + { + mCheckStates[upstreamName] = Qt::Checked; + emit dataChanged(indexFromItem(upstreamFile), indexFromItem(upstreamFile)); + } + + } + } + else if (state == Qt::Unchecked) + { + foreach (const EsmFile *downstreamFile, mFiles) + { + if (downstreamFile->gameFiles().contains(name)) + { + if (mCheckStates.contains(downstreamFile->fileName())) + { + mCheckStates[downstreamFile->fileName()] = Qt::Unchecked; + emit dataChanged(indexFromItem(downstreamFile), indexFromItem(downstreamFile)); + } + } + } + } } ContentSelectorModel::ContentFileList ContentSelectorModel::ContentModel::checkedItems() const From c42e74dadf1160a908dff63a4a1c48763a766202 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 24 Sep 2013 13:17:28 +0200 Subject: [PATCH 051/434] make 4 byte record type accessable from record struct --- apps/opencs/model/doc/saving.cpp | 24 +++++++++++------------- apps/opencs/model/doc/savingstages.hpp | 12 ++++-------- components/esm/loadacti.cpp | 3 +++ components/esm/loadacti.hpp | 2 ++ components/esm/loadalch.cpp | 3 +++ components/esm/loadalch.hpp | 2 ++ components/esm/loadappa.cpp | 3 +++ components/esm/loadappa.hpp | 2 ++ components/esm/loadarmo.cpp | 3 +++ components/esm/loadarmo.hpp | 2 ++ components/esm/loadbody.cpp | 3 +++ components/esm/loadbody.hpp | 2 ++ components/esm/loadbook.cpp | 2 ++ components/esm/loadbook.hpp | 2 ++ components/esm/loadbsgn.cpp | 2 ++ components/esm/loadbsgn.hpp | 2 ++ components/esm/loadcell.cpp | 2 ++ components/esm/loadcell.hpp | 3 ++- components/esm/loadclas.cpp | 2 ++ components/esm/loadclas.hpp | 2 ++ components/esm/loadclot.cpp | 2 ++ components/esm/loadclot.hpp | 2 ++ components/esm/loadcont.cpp | 3 +++ components/esm/loadcont.hpp | 2 ++ components/esm/loadcrea.cpp | 3 +++ components/esm/loadcrea.hpp | 2 ++ components/esm/loadcrec.hpp | 2 ++ components/esm/loaddial.cpp | 2 ++ components/esm/loaddial.hpp | 2 ++ components/esm/loaddoor.cpp | 2 ++ components/esm/loaddoor.hpp | 2 ++ components/esm/loadench.cpp | 2 ++ components/esm/loadench.hpp | 2 ++ components/esm/loadfact.cpp | 3 +++ components/esm/loadfact.hpp | 2 ++ components/esm/loadglob.cpp | 4 ++++ components/esm/loadglob.hpp | 2 ++ components/esm/loadgmst.cpp | 4 ++++ components/esm/loadgmst.hpp | 2 ++ components/esm/loadinfo.cpp | 2 ++ components/esm/loadinfo.hpp | 2 ++ components/esm/loadingr.cpp | 2 ++ components/esm/loadingr.hpp | 2 ++ components/esm/loadland.cpp | 2 ++ components/esm/loadland.hpp | 2 ++ components/esm/loadlevlist.cpp | 5 +++++ components/esm/loadlevlist.hpp | 4 ++++ components/esm/loadligh.cpp | 2 ++ components/esm/loadligh.hpp | 2 ++ components/esm/loadlock.cpp | 2 ++ components/esm/loadlock.hpp | 2 ++ components/esm/loadltex.cpp | 2 ++ components/esm/loadltex.hpp | 2 ++ components/esm/loadmgef.cpp | 2 ++ components/esm/loadmgef.hpp | 2 ++ components/esm/loadmisc.cpp | 2 ++ components/esm/loadmisc.hpp | 2 ++ components/esm/loadnpc.cpp | 2 ++ components/esm/loadnpc.hpp | 2 ++ components/esm/loadnpcc.hpp | 2 ++ components/esm/loadpgrd.cpp | 2 ++ components/esm/loadpgrd.hpp | 2 ++ components/esm/loadprob.cpp | 2 ++ components/esm/loadprob.hpp | 2 ++ components/esm/loadrace.cpp | 3 +++ components/esm/loadrace.hpp | 2 ++ components/esm/loadregn.cpp | 2 ++ components/esm/loadregn.hpp | 2 ++ components/esm/loadrepa.cpp | 2 ++ components/esm/loadrepa.hpp | 2 ++ components/esm/loadscpt.cpp | 3 +++ components/esm/loadscpt.hpp | 2 ++ components/esm/loadskil.cpp | 3 +++ components/esm/loadskil.hpp | 2 ++ components/esm/loadsndg.cpp | 2 ++ components/esm/loadsndg.hpp | 2 ++ components/esm/loadsoun.cpp | 2 ++ components/esm/loadsoun.hpp | 2 ++ components/esm/loadspel.cpp | 2 ++ components/esm/loadspel.hpp | 2 ++ components/esm/loadsscr.cpp | 2 ++ components/esm/loadsscr.hpp | 2 ++ components/esm/loadstat.cpp | 2 ++ components/esm/loadstat.hpp | 2 ++ components/esm/loadtes3.cpp | 1 + components/esm/loadweap.cpp | 2 ++ components/esm/loadweap.hpp | 2 ++ 87 files changed, 204 insertions(+), 22 deletions(-) diff --git a/apps/opencs/model/doc/saving.cpp b/apps/opencs/model/doc/saving.cpp index 7e0b10d66b..c9035cfd43 100644 --- a/apps/opencs/model/doc/saving.cpp +++ b/apps/opencs/model/doc/saving.cpp @@ -1,8 +1,6 @@ #include "saving.hpp" -#include - #include "../world/data.hpp" #include "../world/idcollection.hpp" @@ -18,37 +16,37 @@ CSMDoc::Saving::Saving (Document& document) appendStage (new WriteHeaderStage (mDocument, mState)); appendStage (new WriteCollectionStage > - (mDocument.getData().getGlobals(), mState, ESM::REC_GLOB)); + (mDocument.getData().getGlobals(), mState)); appendStage (new WriteCollectionStage > - (mDocument.getData().getGmsts(), mState, ESM::REC_GMST)); + (mDocument.getData().getGmsts(), mState)); appendStage (new WriteCollectionStage > - (mDocument.getData().getSkills(), mState, ESM::REC_SKIL)); + (mDocument.getData().getSkills(), mState)); appendStage (new WriteCollectionStage > - (mDocument.getData().getClasses(), mState, ESM::REC_CLAS)); + (mDocument.getData().getClasses(), mState)); appendStage (new WriteCollectionStage > - (mDocument.getData().getFactions(), mState, ESM::REC_FACT)); + (mDocument.getData().getFactions(), mState)); appendStage (new WriteCollectionStage > - (mDocument.getData().getRaces(), mState, ESM::REC_RACE)); + (mDocument.getData().getRaces(), mState)); appendStage (new WriteCollectionStage > - (mDocument.getData().getSounds(), mState, ESM::REC_SOUN)); + (mDocument.getData().getSounds(), mState)); appendStage (new WriteCollectionStage > - (mDocument.getData().getScripts(), mState, ESM::REC_SCPT)); + (mDocument.getData().getScripts(), mState)); appendStage (new WriteCollectionStage > - (mDocument.getData().getRegions(), mState, ESM::REC_REGN)); + (mDocument.getData().getRegions(), mState)); appendStage (new WriteCollectionStage > - (mDocument.getData().getBirthsigns(), mState, ESM::REC_BSGN)); + (mDocument.getData().getBirthsigns(), mState)); appendStage (new WriteCollectionStage > - (mDocument.getData().getSpells(), mState, ESM::REC_SPEL)); + (mDocument.getData().getSpells(), mState)); appendStage (new CloseSaveStage (mState)); diff --git a/apps/opencs/model/doc/savingstages.hpp b/apps/opencs/model/doc/savingstages.hpp index 96b1fe17f6..d5c4a69af7 100644 --- a/apps/opencs/model/doc/savingstages.hpp +++ b/apps/opencs/model/doc/savingstages.hpp @@ -1,8 +1,6 @@ #ifndef CSM_DOC_SAVINGSTAGES_H #define CSM_DOC_SAVINGSTAGES_H -#include - #include "stage.hpp" #include "savingstate.hpp" @@ -52,12 +50,10 @@ namespace CSMDoc { const CollectionT& mCollection; SavingState& mState; - ESM::RecNameInts mRecordType; public: - WriteCollectionStage (const CollectionT& collection, SavingState& state, - ESM::RecNameInts recordType); + WriteCollectionStage (const CollectionT& collection, SavingState& state); virtual int setup(); ///< \return number of steps @@ -68,8 +64,8 @@ namespace CSMDoc template WriteCollectionStage::WriteCollectionStage (const CollectionT& collection, - SavingState& state, ESM::RecNameInts recordType) - : mCollection (collection), mState (state), mRecordType (recordType) + SavingState& state) + : mCollection (collection), mState (state) {} template @@ -89,7 +85,7 @@ namespace CSMDoc std::string type; for (int i=0; i<4; ++i) /// \todo make endianess agnostic (change ESMWriter interface?) - type += reinterpret_cast (&mRecordType)[i]; + type += reinterpret_cast (&mCollection.getRecord (stage).mModified.sRecordId)[i]; mState.getWriter().startRecord (type); mState.getWriter().writeHNCString ("NAME", mCollection.getId (stage)); diff --git a/components/esm/loadacti.cpp b/components/esm/loadacti.cpp index dcae845d0a..6ba0df0b36 100644 --- a/components/esm/loadacti.cpp +++ b/components/esm/loadacti.cpp @@ -2,9 +2,12 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Activator::sRecordId = REC_ACTI; + void Activator::load(ESMReader &esm) { mModel = esm.getHNString("MODL"); diff --git a/components/esm/loadacti.hpp b/components/esm/loadacti.hpp index 6b072ee11e..88f27de27e 100644 --- a/components/esm/loadacti.hpp +++ b/components/esm/loadacti.hpp @@ -11,6 +11,8 @@ class ESMWriter; struct Activator { + static unsigned int sRecordId; + std::string mId, mName, mScript, mModel; void load(ESMReader &esm); diff --git a/components/esm/loadalch.cpp b/components/esm/loadalch.cpp index 187069c2ef..f6bfc6a11d 100644 --- a/components/esm/loadalch.cpp +++ b/components/esm/loadalch.cpp @@ -2,9 +2,12 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Potion::sRecordId = REC_ALCH; + void Potion::load(ESMReader &esm) { mModel = esm.getHNString("MODL"); diff --git a/components/esm/loadalch.hpp b/components/esm/loadalch.hpp index 8f0435292e..141765aa82 100644 --- a/components/esm/loadalch.hpp +++ b/components/esm/loadalch.hpp @@ -17,6 +17,8 @@ class ESMWriter; struct Potion { + static unsigned int sRecordId; + struct ALDTstruct { float mWeight; diff --git a/components/esm/loadappa.cpp b/components/esm/loadappa.cpp index 01233a0558..29ea78acc0 100644 --- a/components/esm/loadappa.cpp +++ b/components/esm/loadappa.cpp @@ -2,9 +2,12 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Apparatus::sRecordId = REC_APPA; + void Apparatus::load(ESMReader &esm) { // we will not treat duplicated subrecords as errors here diff --git a/components/esm/loadappa.hpp b/components/esm/loadappa.hpp index d47643c6ce..adc8e071f1 100644 --- a/components/esm/loadappa.hpp +++ b/components/esm/loadappa.hpp @@ -15,6 +15,8 @@ class ESMWriter; struct Apparatus { + static unsigned int sRecordId; + enum AppaType { MortarPestle = 0, diff --git a/components/esm/loadarmo.cpp b/components/esm/loadarmo.cpp index 4dbdf1314d..ec8ff4f20c 100644 --- a/components/esm/loadarmo.cpp +++ b/components/esm/loadarmo.cpp @@ -2,6 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { @@ -28,6 +29,8 @@ void PartReferenceList::save(ESMWriter &esm) const } } +unsigned int Armor::sRecordId = REC_ARMO; + void Armor::load(ESMReader &esm) { mModel = esm.getHNString("MODL"); diff --git a/components/esm/loadarmo.hpp b/components/esm/loadarmo.hpp index 5a38605e3d..991f4e1855 100644 --- a/components/esm/loadarmo.hpp +++ b/components/esm/loadarmo.hpp @@ -61,6 +61,8 @@ struct PartReferenceList struct Armor { + static unsigned int sRecordId; + enum Type { Helmet = 0, diff --git a/components/esm/loadbody.cpp b/components/esm/loadbody.cpp index a5d986f65d..4015e6c91a 100644 --- a/components/esm/loadbody.cpp +++ b/components/esm/loadbody.cpp @@ -2,9 +2,12 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int BodyPart::sRecordId = REC_BODY; + void BodyPart::load(ESMReader &esm) { diff --git a/components/esm/loadbody.hpp b/components/esm/loadbody.hpp index a8fd36aef4..9623caa31e 100644 --- a/components/esm/loadbody.hpp +++ b/components/esm/loadbody.hpp @@ -11,6 +11,8 @@ class ESMWriter; struct BodyPart { + static unsigned int sRecordId; + enum MeshPart { MP_Head = 0, diff --git a/components/esm/loadbook.cpp b/components/esm/loadbook.cpp index d9db118899..c8b7e94789 100644 --- a/components/esm/loadbook.cpp +++ b/components/esm/loadbook.cpp @@ -2,9 +2,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Book::sRecordId = REC_BOOK; void Book::load(ESMReader &esm) { diff --git a/components/esm/loadbook.hpp b/components/esm/loadbook.hpp index 688e9dd75e..f96fbd709b 100644 --- a/components/esm/loadbook.hpp +++ b/components/esm/loadbook.hpp @@ -14,6 +14,8 @@ class ESMWriter; struct Book { + static unsigned int sRecordId; + struct BKDTstruct { float mWeight; diff --git a/components/esm/loadbsgn.cpp b/components/esm/loadbsgn.cpp index 9d19f02c7e..55e1e7f65a 100644 --- a/components/esm/loadbsgn.cpp +++ b/components/esm/loadbsgn.cpp @@ -2,9 +2,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int BirthSign::sRecordId = REC_BSGN; void BirthSign::load(ESMReader &esm) { diff --git a/components/esm/loadbsgn.hpp b/components/esm/loadbsgn.hpp index 1ecb5e418e..9f9435c8f1 100644 --- a/components/esm/loadbsgn.hpp +++ b/components/esm/loadbsgn.hpp @@ -13,6 +13,8 @@ class ESMWriter; struct BirthSign { + static unsigned int sRecordId; + std::string mId, mName, mDescription, mTexture; // List of powers and abilities that come with this birth sign. diff --git a/components/esm/loadcell.cpp b/components/esm/loadcell.cpp index 9b3aa09e86..c22c1b22b6 100644 --- a/components/esm/loadcell.cpp +++ b/components/esm/loadcell.cpp @@ -7,9 +7,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Cell::sRecordId = REC_CELL; /// Some overloaded compare operators. bool operator==(const MovedCellRef& ref, int pRefnum) diff --git a/components/esm/loadcell.hpp b/components/esm/loadcell.hpp index c417fceab8..61d586b9d8 100644 --- a/components/esm/loadcell.hpp +++ b/components/esm/loadcell.hpp @@ -16,7 +16,6 @@ namespace MWWorld namespace ESM { - class ESMReader; class ESMWriter; @@ -55,6 +54,8 @@ typedef std::list CellRefTracker; */ struct Cell { + static unsigned int sRecordId; + enum Flags { Interior = 0x01, // Interior cell diff --git a/components/esm/loadclas.cpp b/components/esm/loadclas.cpp index ef07430c7a..33489eec46 100644 --- a/components/esm/loadclas.cpp +++ b/components/esm/loadclas.cpp @@ -4,9 +4,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Class::sRecordId = REC_CLAS; const Class::Specialization Class::sSpecializationIds[3] = { Class::Combat, diff --git a/components/esm/loadclas.hpp b/components/esm/loadclas.hpp index f241dca8da..3e489bb58a 100644 --- a/components/esm/loadclas.hpp +++ b/components/esm/loadclas.hpp @@ -17,6 +17,8 @@ class ESMWriter; // class struct Class { + static unsigned int sRecordId; + enum AutoCalc { Weapon = 0x00001, diff --git a/components/esm/loadclot.cpp b/components/esm/loadclot.cpp index c623155dfa..d64564d77f 100644 --- a/components/esm/loadclot.cpp +++ b/components/esm/loadclot.cpp @@ -2,9 +2,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Clothing::sRecordId = REC_CLOT; void Clothing::load(ESMReader &esm) { diff --git a/components/esm/loadclot.hpp b/components/esm/loadclot.hpp index 13fae865b4..50896622aa 100644 --- a/components/esm/loadclot.hpp +++ b/components/esm/loadclot.hpp @@ -17,6 +17,8 @@ class ESMWriter; struct Clothing { + static unsigned int sRecordId; + enum Type { Pants = 0, diff --git a/components/esm/loadcont.cpp b/components/esm/loadcont.cpp index 0cbb4acd1e..7bdf9f05b1 100644 --- a/components/esm/loadcont.cpp +++ b/components/esm/loadcont.cpp @@ -2,6 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { @@ -24,6 +25,8 @@ void InventoryList::save(ESMWriter &esm) const } } + unsigned int Container::sRecordId = REC_CONT; + void Container::load(ESMReader &esm) { mModel = esm.getHNString("MODL"); diff --git a/components/esm/loadcont.hpp b/components/esm/loadcont.hpp index c854b52907..2808b67b56 100644 --- a/components/esm/loadcont.hpp +++ b/components/esm/loadcont.hpp @@ -32,6 +32,8 @@ struct InventoryList struct Container { + static unsigned int sRecordId; + enum Flags { Organic = 1, // Objects cannot be placed in this container diff --git a/components/esm/loadcrea.cpp b/components/esm/loadcrea.cpp index 30b70b35b6..650de08011 100644 --- a/components/esm/loadcrea.cpp +++ b/components/esm/loadcrea.cpp @@ -2,9 +2,12 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Creature::sRecordId = REC_CREA; + void Creature::load(ESMReader &esm) { mPersistent = esm.getRecordFlags() & 0x0400; diff --git a/components/esm/loadcrea.hpp b/components/esm/loadcrea.hpp index 80e0fbd1c9..99c4f52257 100644 --- a/components/esm/loadcrea.hpp +++ b/components/esm/loadcrea.hpp @@ -20,6 +20,8 @@ class ESMWriter; struct Creature { + static unsigned int sRecordId; + // Default is 0x48? enum Flags { diff --git a/components/esm/loadcrec.hpp b/components/esm/loadcrec.hpp index 2b840ccf46..280739acad 100644 --- a/components/esm/loadcrec.hpp +++ b/components/esm/loadcrec.hpp @@ -17,6 +17,8 @@ class ESMWriter; /// Changes a creature struct LoadCREC { + static unsigned int sRecordId; + std::string mId; void load(ESMReader &esm) diff --git a/components/esm/loaddial.cpp b/components/esm/loaddial.cpp index e014ca37e8..f64ecb5a06 100644 --- a/components/esm/loaddial.cpp +++ b/components/esm/loaddial.cpp @@ -2,9 +2,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Dialogue::sRecordId = REC_DIAL; void Dialogue::load(ESMReader &esm) { diff --git a/components/esm/loaddial.hpp b/components/esm/loaddial.hpp index 0fe5027dc9..3997d77537 100644 --- a/components/esm/loaddial.hpp +++ b/components/esm/loaddial.hpp @@ -19,6 +19,8 @@ class ESMWriter; struct Dialogue { + static unsigned int sRecordId; + enum Type { Topic = 0, diff --git a/components/esm/loaddoor.cpp b/components/esm/loaddoor.cpp index f666ac67a9..c56b063379 100644 --- a/components/esm/loaddoor.cpp +++ b/components/esm/loaddoor.cpp @@ -2,9 +2,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Door::sRecordId = REC_DOOR; void Door::load(ESMReader &esm) { diff --git a/components/esm/loaddoor.hpp b/components/esm/loaddoor.hpp index 2b927c56e0..ee2b7f7ac5 100644 --- a/components/esm/loaddoor.hpp +++ b/components/esm/loaddoor.hpp @@ -11,6 +11,8 @@ class ESMWriter; struct Door { + static unsigned int sRecordId; + std::string mId, mName, mModel, mScript, mOpenSound, mCloseSound; void load(ESMReader &esm); diff --git a/components/esm/loadench.cpp b/components/esm/loadench.cpp index 4b4c3a1ec5..a1e885f23b 100644 --- a/components/esm/loadench.cpp +++ b/components/esm/loadench.cpp @@ -2,9 +2,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Enchantment::sRecordId = REC_ENCH; void Enchantment::load(ESMReader &esm) { diff --git a/components/esm/loadench.hpp b/components/esm/loadench.hpp index 3cdc3a0bd0..f6ba8c6ab3 100644 --- a/components/esm/loadench.hpp +++ b/components/esm/loadench.hpp @@ -17,6 +17,8 @@ class ESMWriter; struct Enchantment { + static unsigned int sRecordId; + enum Type { CastOnce = 0, diff --git a/components/esm/loadfact.cpp b/components/esm/loadfact.cpp index c8be518028..61fa902639 100644 --- a/components/esm/loadfact.cpp +++ b/components/esm/loadfact.cpp @@ -4,9 +4,12 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Faction::sRecordId = REC_FACT; + int& Faction::FADTstruct::getSkill (int index, bool ignored) { if (index<0 || index>=6) diff --git a/components/esm/loadfact.hpp b/components/esm/loadfact.hpp index 11f65a87f5..9c257e068e 100644 --- a/components/esm/loadfact.hpp +++ b/components/esm/loadfact.hpp @@ -29,6 +29,8 @@ struct RankData struct Faction { + static unsigned int sRecordId; + std::string mId, mName; struct FADTstruct diff --git a/components/esm/loadglob.cpp b/components/esm/loadglob.cpp index e1c2d44087..a78ed1a1be 100644 --- a/components/esm/loadglob.cpp +++ b/components/esm/loadglob.cpp @@ -1,7 +1,11 @@ #include "loadglob.hpp" +#include "defs.hpp" + namespace ESM { + unsigned int Global::sRecordId = REC_GLOB; + void Global::load (ESMReader &esm) { mValue.read (esm, ESM::Variant::Format_Global); diff --git a/components/esm/loadglob.hpp b/components/esm/loadglob.hpp index 06ff97ef20..51b2e2dc98 100644 --- a/components/esm/loadglob.hpp +++ b/components/esm/loadglob.hpp @@ -17,6 +17,8 @@ class ESMWriter; struct Global { + static unsigned int sRecordId; + std::string mId; Variant mValue; diff --git a/components/esm/loadgmst.cpp b/components/esm/loadgmst.cpp index 3a7df45065..21d66339a9 100644 --- a/components/esm/loadgmst.cpp +++ b/components/esm/loadgmst.cpp @@ -1,7 +1,11 @@ #include "loadgmst.hpp" +#include "defs.hpp" + namespace ESM { + unsigned int GameSetting::sRecordId = REC_GMST; + void GameSetting::load (ESMReader &esm) { mValue.read (esm, ESM::Variant::Format_Gmst); diff --git a/components/esm/loadgmst.hpp b/components/esm/loadgmst.hpp index 9c37c7da09..6b66ac832d 100644 --- a/components/esm/loadgmst.hpp +++ b/components/esm/loadgmst.hpp @@ -18,6 +18,8 @@ class ESMWriter; struct GameSetting { + static unsigned int sRecordId; + std::string mId; Variant mValue; diff --git a/components/esm/loadinfo.cpp b/components/esm/loadinfo.cpp index 1985da2cd9..4f248cc65f 100644 --- a/components/esm/loadinfo.cpp +++ b/components/esm/loadinfo.cpp @@ -2,9 +2,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int DialInfo::sRecordId = REC_INFO; void DialInfo::load(ESMReader &esm) { diff --git a/components/esm/loadinfo.hpp b/components/esm/loadinfo.hpp index 351768e96f..2589ea7b87 100644 --- a/components/esm/loadinfo.hpp +++ b/components/esm/loadinfo.hpp @@ -20,6 +20,8 @@ class ESMWriter; struct DialInfo { + static unsigned int sRecordId; + enum Gender { Male = 0, diff --git a/components/esm/loadingr.cpp b/components/esm/loadingr.cpp index 1bc9ae41c6..0e02433621 100644 --- a/components/esm/loadingr.cpp +++ b/components/esm/loadingr.cpp @@ -2,9 +2,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Ingredient::sRecordId = REC_INGR; void Ingredient::load(ESMReader &esm) { diff --git a/components/esm/loadingr.hpp b/components/esm/loadingr.hpp index 03e67924c5..85f2d5e7d2 100644 --- a/components/esm/loadingr.hpp +++ b/components/esm/loadingr.hpp @@ -15,6 +15,8 @@ class ESMWriter; struct Ingredient { + static unsigned int sRecordId; + struct IRDTstruct { float mWeight; diff --git a/components/esm/loadland.cpp b/components/esm/loadland.cpp index 8e54bcc5c9..ede200d79d 100644 --- a/components/esm/loadland.cpp +++ b/components/esm/loadland.cpp @@ -2,9 +2,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Land::sRecordId = REC_LAND; void Land::LandData::save(ESMWriter &esm) { diff --git a/components/esm/loadland.hpp b/components/esm/loadland.hpp index 3d3bcd67bb..5649f99801 100644 --- a/components/esm/loadland.hpp +++ b/components/esm/loadland.hpp @@ -17,6 +17,8 @@ class ESMWriter; struct Land { + static unsigned int sRecordId; + Land(); ~Land(); diff --git a/components/esm/loadlevlist.cpp b/components/esm/loadlevlist.cpp index ab3f5e9e66..6385b9a718 100644 --- a/components/esm/loadlevlist.cpp +++ b/components/esm/loadlevlist.cpp @@ -2,6 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { @@ -52,4 +53,8 @@ void LeveledListBase::save(ESMWriter &esm) const mChanceNone = 0; mList.clear(); } + + unsigned int CreatureLevList::sRecordId = REC_LEVC; + + unsigned int ItemLevList::sRecordId = REC_LEVI; } diff --git a/components/esm/loadlevlist.hpp b/components/esm/loadlevlist.hpp index f5fb7fd5b1..9dcc6177a1 100644 --- a/components/esm/loadlevlist.hpp +++ b/components/esm/loadlevlist.hpp @@ -59,6 +59,8 @@ struct LeveledListBase struct CreatureLevList: LeveledListBase { + static unsigned int sRecordId; + CreatureLevList() { mRecName = "CNAM"; @@ -67,6 +69,8 @@ struct CreatureLevList: LeveledListBase struct ItemLevList: LeveledListBase { + static unsigned int sRecordId; + ItemLevList() { mRecName = "INAM"; diff --git a/components/esm/loadligh.cpp b/components/esm/loadligh.cpp index 3f279c7c83..c02bb46b69 100644 --- a/components/esm/loadligh.cpp +++ b/components/esm/loadligh.cpp @@ -2,9 +2,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Light::sRecordId = REC_LIGH; void Light::load(ESMReader &esm) { diff --git a/components/esm/loadligh.hpp b/components/esm/loadligh.hpp index 9a341f0de5..74eb37197e 100644 --- a/components/esm/loadligh.hpp +++ b/components/esm/loadligh.hpp @@ -16,6 +16,8 @@ class ESMWriter; struct Light { + static unsigned int sRecordId; + enum Flags { Dynamic = 0x001, diff --git a/components/esm/loadlock.cpp b/components/esm/loadlock.cpp index 318769ec08..9ffce78a7d 100644 --- a/components/esm/loadlock.cpp +++ b/components/esm/loadlock.cpp @@ -2,9 +2,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Lockpick::sRecordId = REC_LOCK; void Lockpick::load(ESMReader &esm) { diff --git a/components/esm/loadlock.hpp b/components/esm/loadlock.hpp index aea5a4f31d..c44e2b0063 100644 --- a/components/esm/loadlock.hpp +++ b/components/esm/loadlock.hpp @@ -11,6 +11,8 @@ class ESMWriter; struct Lockpick { + static unsigned int sRecordId; + struct Data { float mWeight; diff --git a/components/esm/loadltex.cpp b/components/esm/loadltex.cpp index dc1bc164b4..bd28c84883 100644 --- a/components/esm/loadltex.cpp +++ b/components/esm/loadltex.cpp @@ -2,9 +2,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int LandTexture::sRecordId = REC_LTEX; void LandTexture::load(ESMReader &esm) { diff --git a/components/esm/loadltex.hpp b/components/esm/loadltex.hpp index 3d08169484..5e84428b29 100644 --- a/components/esm/loadltex.hpp +++ b/components/esm/loadltex.hpp @@ -27,6 +27,8 @@ class ESMWriter; struct LandTexture { + static unsigned int sRecordId; + std::string mId, mTexture; int mIndex; diff --git a/components/esm/loadmgef.cpp b/components/esm/loadmgef.cpp index 9eaeff7046..332c27786f 100644 --- a/components/esm/loadmgef.cpp +++ b/components/esm/loadmgef.cpp @@ -6,6 +6,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace { @@ -34,6 +35,7 @@ namespace namespace ESM { + unsigned int MagicEffect::sRecordId = REC_MGEF; void MagicEffect::load(ESMReader &esm) { diff --git a/components/esm/loadmgef.hpp b/components/esm/loadmgef.hpp index 220b3bdf39..613cbd2d86 100644 --- a/components/esm/loadmgef.hpp +++ b/components/esm/loadmgef.hpp @@ -12,6 +12,8 @@ class ESMWriter; struct MagicEffect { + static unsigned int sRecordId; + enum Flags { TargetSkill = 0x1, // Affects a specific skill, which is specified elsewhere in the effect structure. diff --git a/components/esm/loadmisc.cpp b/components/esm/loadmisc.cpp index a183821b74..2ca09e8aec 100644 --- a/components/esm/loadmisc.cpp +++ b/components/esm/loadmisc.cpp @@ -2,9 +2,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Miscellaneous::sRecordId = REC_MISC; void Miscellaneous::load(ESMReader &esm) { diff --git a/components/esm/loadmisc.hpp b/components/esm/loadmisc.hpp index 452eea53e0..576bd18c0f 100644 --- a/components/esm/loadmisc.hpp +++ b/components/esm/loadmisc.hpp @@ -16,6 +16,8 @@ class ESMWriter; struct Miscellaneous { + static unsigned int sRecordId; + struct MCDTstruct { float mWeight; diff --git a/components/esm/loadnpc.cpp b/components/esm/loadnpc.cpp index 7c26cb549b..9fff2d885d 100644 --- a/components/esm/loadnpc.cpp +++ b/components/esm/loadnpc.cpp @@ -2,9 +2,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int NPC::sRecordId = REC_NPC_; void NPC::load(ESMReader &esm) { diff --git a/components/esm/loadnpc.hpp b/components/esm/loadnpc.hpp index 009548c596..d9e691669c 100644 --- a/components/esm/loadnpc.hpp +++ b/components/esm/loadnpc.hpp @@ -20,6 +20,8 @@ class ESMWriter; struct NPC { + static unsigned int sRecordId; + // Services enum Services { diff --git a/components/esm/loadnpcc.hpp b/components/esm/loadnpcc.hpp index f023fd2172..c87c2545f7 100644 --- a/components/esm/loadnpcc.hpp +++ b/components/esm/loadnpcc.hpp @@ -78,6 +78,8 @@ class ESMWriter; struct LoadNPCC { + static unsigned int sRecordId; + std::string mId; void load(ESMReader &esm) diff --git a/components/esm/loadpgrd.cpp b/components/esm/loadpgrd.cpp index 65d1f80553..3b5330e9fd 100644 --- a/components/esm/loadpgrd.cpp +++ b/components/esm/loadpgrd.cpp @@ -2,9 +2,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Pathgrid::sRecordId = REC_PGRD; void Pathgrid::load(ESMReader &esm) { diff --git a/components/esm/loadpgrd.hpp b/components/esm/loadpgrd.hpp index d14433a786..9ee49552db 100644 --- a/components/esm/loadpgrd.hpp +++ b/components/esm/loadpgrd.hpp @@ -15,6 +15,8 @@ class ESMWriter; */ struct Pathgrid { + static unsigned int sRecordId; + struct DATAstruct { int mX, mY; // Grid location, matches cell for exterior cells diff --git a/components/esm/loadprob.cpp b/components/esm/loadprob.cpp index 0fb4c97507..caa3d7e0e7 100644 --- a/components/esm/loadprob.cpp +++ b/components/esm/loadprob.cpp @@ -2,9 +2,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Probe::sRecordId = REC_PROB; void Probe::load(ESMReader &esm) { diff --git a/components/esm/loadprob.hpp b/components/esm/loadprob.hpp index d0a8256ab6..b89b2ddeb6 100644 --- a/components/esm/loadprob.hpp +++ b/components/esm/loadprob.hpp @@ -11,6 +11,8 @@ class ESMWriter; struct Probe { + static unsigned int sRecordId; + struct Data { float mWeight; diff --git a/components/esm/loadrace.cpp b/components/esm/loadrace.cpp index e9e1d0d797..e50e43a744 100644 --- a/components/esm/loadrace.cpp +++ b/components/esm/loadrace.cpp @@ -2,9 +2,12 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Race::sRecordId = REC_RACE; + int Race::MaleFemale::getValue (bool male) const { return male ? mMale : mFemale; diff --git a/components/esm/loadrace.hpp b/components/esm/loadrace.hpp index a53a980701..7d5736d9b7 100644 --- a/components/esm/loadrace.hpp +++ b/components/esm/loadrace.hpp @@ -17,6 +17,8 @@ class ESMWriter; struct Race { + static unsigned int sRecordId; + struct SkillBonus { int mSkill; // SkillEnum diff --git a/components/esm/loadregn.cpp b/components/esm/loadregn.cpp index fd42b9ee83..fa4271e26a 100644 --- a/components/esm/loadregn.cpp +++ b/components/esm/loadregn.cpp @@ -2,9 +2,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Region::sRecordId = REC_REGN; void Region::load(ESMReader &esm) { diff --git a/components/esm/loadregn.hpp b/components/esm/loadregn.hpp index a6075d65ae..1992c951b4 100644 --- a/components/esm/loadregn.hpp +++ b/components/esm/loadregn.hpp @@ -18,6 +18,8 @@ class ESMWriter; struct Region { + static unsigned int sRecordId; + #pragma pack(push) #pragma pack(1) struct WEATstruct diff --git a/components/esm/loadrepa.cpp b/components/esm/loadrepa.cpp index 59bfa01695..a7132828d4 100644 --- a/components/esm/loadrepa.cpp +++ b/components/esm/loadrepa.cpp @@ -2,9 +2,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Repair::sRecordId = REC_REPA; void Repair::load(ESMReader &esm) { diff --git a/components/esm/loadrepa.hpp b/components/esm/loadrepa.hpp index 771e7ead07..5b404b0e4c 100644 --- a/components/esm/loadrepa.hpp +++ b/components/esm/loadrepa.hpp @@ -11,6 +11,8 @@ class ESMWriter; struct Repair { + static unsigned int sRecordId; + struct Data { float mWeight; diff --git a/components/esm/loadscpt.cpp b/components/esm/loadscpt.cpp index 8afb85602d..30460c17a6 100644 --- a/components/esm/loadscpt.cpp +++ b/components/esm/loadscpt.cpp @@ -2,6 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { @@ -12,6 +13,8 @@ struct SCHD Script::SCHDstruct mData; }; + unsigned int Script::sRecordId = REC_SCPT; + void Script::load(ESMReader &esm) { SCHD data; diff --git a/components/esm/loadscpt.hpp b/components/esm/loadscpt.hpp index 450224faa4..d5200d4c12 100644 --- a/components/esm/loadscpt.hpp +++ b/components/esm/loadscpt.hpp @@ -19,6 +19,8 @@ class ESMWriter; class Script { public: + static unsigned int sRecordId; + struct SCHDstruct { /* Script name. diff --git a/components/esm/loadskil.cpp b/components/esm/loadskil.cpp index f6a2c49503..b6724e9381 100644 --- a/components/esm/loadskil.cpp +++ b/components/esm/loadskil.cpp @@ -6,6 +6,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { @@ -126,6 +127,8 @@ namespace ESM HandToHand }}; + unsigned int Skill::sRecordId = REC_SKIL; + void Skill::load(ESMReader &esm) { esm.getHNT(mIndex, "INDX"); diff --git a/components/esm/loadskil.hpp b/components/esm/loadskil.hpp index 2436173cbb..1b9db5bcff 100644 --- a/components/esm/loadskil.hpp +++ b/components/esm/loadskil.hpp @@ -19,6 +19,8 @@ class ESMWriter; struct Skill { + static unsigned int sRecordId; + std::string mId; struct SKDTstruct diff --git a/components/esm/loadsndg.cpp b/components/esm/loadsndg.cpp index 9b992c9606..1a8ca63354 100644 --- a/components/esm/loadsndg.cpp +++ b/components/esm/loadsndg.cpp @@ -2,9 +2,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int SoundGenerator::sRecordId = REC_SNDG; void SoundGenerator::load(ESMReader &esm) { diff --git a/components/esm/loadsndg.hpp b/components/esm/loadsndg.hpp index 2756676ef1..5509661c18 100644 --- a/components/esm/loadsndg.hpp +++ b/components/esm/loadsndg.hpp @@ -15,6 +15,8 @@ class ESMWriter; struct SoundGenerator { + static unsigned int sRecordId; + enum Type { LeftFoot = 0, diff --git a/components/esm/loadsoun.cpp b/components/esm/loadsoun.cpp index 0f6b0f84a7..49c9eb54e2 100644 --- a/components/esm/loadsoun.cpp +++ b/components/esm/loadsoun.cpp @@ -2,9 +2,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Sound::sRecordId = REC_SOUN; void Sound::load(ESMReader &esm) { diff --git a/components/esm/loadsoun.hpp b/components/esm/loadsoun.hpp index 6c9bb1fed3..04a0984fda 100644 --- a/components/esm/loadsoun.hpp +++ b/components/esm/loadsoun.hpp @@ -16,6 +16,8 @@ struct SOUNstruct struct Sound { + static unsigned int sRecordId; + SOUNstruct mData; std::string mId, mSound; diff --git a/components/esm/loadspel.cpp b/components/esm/loadspel.cpp index 5c0bd956f7..2c98d796d3 100644 --- a/components/esm/loadspel.cpp +++ b/components/esm/loadspel.cpp @@ -2,9 +2,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Spell::sRecordId = REC_SPEL; void Spell::load(ESMReader &esm) { diff --git a/components/esm/loadspel.hpp b/components/esm/loadspel.hpp index b34bd29f18..cbf5366c4b 100644 --- a/components/esm/loadspel.hpp +++ b/components/esm/loadspel.hpp @@ -13,6 +13,8 @@ class ESMWriter; struct Spell { + static unsigned int sRecordId; + enum SpellType { ST_Spell = 0, // Normal spell, must be cast and costs mana diff --git a/components/esm/loadsscr.cpp b/components/esm/loadsscr.cpp index f51b7be479..69b04bb237 100644 --- a/components/esm/loadsscr.cpp +++ b/components/esm/loadsscr.cpp @@ -2,9 +2,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int StartScript::sRecordId = REC_SSCR; void StartScript::load(ESMReader &esm) { diff --git a/components/esm/loadsscr.hpp b/components/esm/loadsscr.hpp index 2326f00f43..d09ad883eb 100644 --- a/components/esm/loadsscr.hpp +++ b/components/esm/loadsscr.hpp @@ -19,6 +19,8 @@ class ESMWriter; struct StartScript { + static unsigned int sRecordId; + std::string mData; std::string mId, mScript; diff --git a/components/esm/loadstat.cpp b/components/esm/loadstat.cpp index 38206422bb..a71f22dc23 100644 --- a/components/esm/loadstat.cpp +++ b/components/esm/loadstat.cpp @@ -2,9 +2,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Static::sRecordId = REC_STAT; void Static::load(ESMReader &esm) { diff --git a/components/esm/loadstat.hpp b/components/esm/loadstat.hpp index df42c0c491..d912d10583 100644 --- a/components/esm/loadstat.hpp +++ b/components/esm/loadstat.hpp @@ -22,6 +22,8 @@ class ESMWriter; struct Static { + static unsigned int sRecordId; + std::string mId, mModel; void load(ESMReader &esm); diff --git a/components/esm/loadtes3.cpp b/components/esm/loadtes3.cpp index 74d578ba7d..a86c9b6f4f 100644 --- a/components/esm/loadtes3.cpp +++ b/components/esm/loadtes3.cpp @@ -4,6 +4,7 @@ #include "esmcommon.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" void ESM::Header::blank() { diff --git a/components/esm/loadweap.cpp b/components/esm/loadweap.cpp index e21d8924a1..1d0b149df1 100644 --- a/components/esm/loadweap.cpp +++ b/components/esm/loadweap.cpp @@ -2,9 +2,11 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" namespace ESM { + unsigned int Weapon::sRecordId = REC_WEAP; void Weapon::load(ESMReader &esm) { diff --git a/components/esm/loadweap.hpp b/components/esm/loadweap.hpp index 42810d3afb..fde716b916 100644 --- a/components/esm/loadweap.hpp +++ b/components/esm/loadweap.hpp @@ -15,6 +15,8 @@ class ESMWriter; struct Weapon { + static unsigned int sRecordId; + enum Type { ShortBladeOneHand = 0, From e4fdebc85b917e5608c9da75eb9af8180c766105 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 24 Sep 2013 13:53:19 +0200 Subject: [PATCH 052/434] added saving for referenceable records --- apps/opencs/model/doc/saving.cpp | 2 ++ apps/opencs/model/doc/savingstages.cpp | 15 ++++++++++ apps/opencs/model/doc/savingstages.hpp | 17 +++++++++++ apps/opencs/model/world/refidcollection.cpp | 5 ++++ apps/opencs/model/world/refidcollection.hpp | 7 +++++ apps/opencs/model/world/refiddata.cpp | 13 +++++++++ apps/opencs/model/world/refiddata.hpp | 32 +++++++++++++++++++++ 7 files changed, 91 insertions(+) diff --git a/apps/opencs/model/doc/saving.cpp b/apps/opencs/model/doc/saving.cpp index c9035cfd43..98af661fdf 100644 --- a/apps/opencs/model/doc/saving.cpp +++ b/apps/opencs/model/doc/saving.cpp @@ -48,6 +48,8 @@ CSMDoc::Saving::Saving (Document& document) appendStage (new WriteCollectionStage > (mDocument.getData().getSpells(), mState)); + appendStage (new WriteRefIdCollectionStage (mDocument, mState)); + appendStage (new CloseSaveStage (mState)); diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index fd62345940..5da92f014f 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -67,6 +67,21 @@ void CSMDoc::WriteHeaderStage::perform (int stage, std::vector& mes } +CSMDoc::WriteRefIdCollectionStage::WriteRefIdCollectionStage (Document& document, SavingState& state) +: mDocument (document), mState (state) +{} + +int CSMDoc::WriteRefIdCollectionStage::setup() +{ + return mDocument.getData().getReferenceables().getSize(); +} + +void CSMDoc::WriteRefIdCollectionStage::perform (int stage, std::vector& messages) +{ + mDocument.getData().getReferenceables().save (stage, mState.getWriter()); +} + + CSMDoc::CloseSaveStage::CloseSaveStage (SavingState& state) : mState (state) {} diff --git a/apps/opencs/model/doc/savingstages.hpp b/apps/opencs/model/doc/savingstages.hpp index d5c4a69af7..2d32f38cbd 100644 --- a/apps/opencs/model/doc/savingstages.hpp +++ b/apps/opencs/model/doc/savingstages.hpp @@ -99,6 +99,23 @@ namespace CSMDoc } + class WriteRefIdCollectionStage : public Stage + { + Document& mDocument; + SavingState& mState; + + public: + + WriteRefIdCollectionStage (Document& document, SavingState& state); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, std::vector& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; + + class CloseSaveStage : public Stage { SavingState& mState; diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index cda2711cc7..3a6f70d310 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -539,3 +539,8 @@ std::vector CSMWorld::RefIdCollection::getIds (bool listDeleted) co { return mData.getIds (listDeleted); } + +void CSMWorld::RefIdCollection::save (int index, ESM::ESMWriter& writer) const +{ + mData.save (index, writer); +} \ No newline at end of file diff --git a/apps/opencs/model/world/refidcollection.hpp b/apps/opencs/model/world/refidcollection.hpp index 22f83150de..a479735db5 100644 --- a/apps/opencs/model/world/refidcollection.hpp +++ b/apps/opencs/model/world/refidcollection.hpp @@ -9,6 +9,11 @@ #include "collectionbase.hpp" #include "refiddata.hpp" +namespace ESM +{ + class ESMWriter; +} + namespace CSMWorld { class RefIdAdapter; @@ -94,6 +99,8 @@ namespace CSMWorld ///< Return a sorted collection of all IDs /// /// \param listDeleted include deleted record in the list + + void save (int index, ESM::ESMWriter& writer) const; }; } diff --git a/apps/opencs/model/world/refiddata.cpp b/apps/opencs/model/world/refiddata.cpp index 9457937f1e..8f59b0fe74 100644 --- a/apps/opencs/model/world/refiddata.cpp +++ b/apps/opencs/model/world/refiddata.cpp @@ -218,3 +218,16 @@ std::vector CSMWorld::RefIdData::getIds (bool listDeleted) const return ids; } + +void CSMWorld::RefIdData::save (int index, ESM::ESMWriter& writer) const +{ + LocalIndex localIndex = globalToLocalIndex (index); + + std::map::const_iterator iter = + mRecordContainers.find (localIndex.second); + + if (iter==mRecordContainers.end()) + throw std::logic_error ("invalid local index type"); + + iter->second->save (localIndex.first, writer); +} \ No newline at end of file diff --git a/apps/opencs/model/world/refiddata.hpp b/apps/opencs/model/world/refiddata.hpp index e221fbc7c2..9595ab23b5 100644 --- a/apps/opencs/model/world/refiddata.hpp +++ b/apps/opencs/model/world/refiddata.hpp @@ -23,6 +23,7 @@ #include #include #include +#include #include "record.hpp" #include "universalid.hpp" @@ -51,6 +52,8 @@ namespace CSMWorld virtual void erase (int index, int count) = 0; virtual std::string getId (int index) const = 0; + + virtual void save (int index, ESM::ESMWriter& writer) const = 0; }; template @@ -71,6 +74,8 @@ namespace CSMWorld virtual void erase (int index, int count); virtual std::string getId (int index) const; + + virtual void save (int index, ESM::ESMWriter& writer) const; }; template @@ -123,6 +128,31 @@ namespace CSMWorld return mContainer.at (index).get().mId; } + template + void RefIdDataContainer::save (int index, ESM::ESMWriter& writer) const + { + CSMWorld::RecordBase::State state = mContainer.at (index).mState; + + if (state==CSMWorld::RecordBase::State_Modified || + state==CSMWorld::RecordBase::State_ModifiedOnly) + { + std::string type; + for (int i=0; i<4; ++i) + /// \todo make endianess agnostic (change ESMWriter interface?) + type += reinterpret_cast (&mContainer.at (index).mModified.sRecordId)[i]; + + writer.startRecord (type); + writer.writeHNCString ("NAME", getId (index)); + mContainer.at (index).mModified.save (writer); + writer.endRecord (type); + } + else if (state==CSMWorld::RecordBase::State_Deleted) + { + /// \todo write record with delete flag + } + } + + class RefIdData { public: @@ -187,6 +217,8 @@ namespace CSMWorld ///< Return a sorted collection of all IDs /// /// \param listDeleted include deleted record in the list + + void save (int index, ESM::ESMWriter& writer) const; }; } From 830530bd063e9a14819c6cf6cb263d6e4a1b32c4 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 24 Sep 2013 17:08:24 +0200 Subject: [PATCH 053/434] set record count in TES3 header --- apps/opencs/model/doc/savingstages.cpp | 5 ++++- apps/opencs/model/world/data.cpp | 29 ++++++++++++++++++++++++++ apps/opencs/model/world/data.hpp | 5 +++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index 5da92f014f..ee2943ef45 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -48,7 +48,10 @@ void CSMDoc::WriteHeaderStage::perform (int stage, std::vector& mes /// \todo fill in missing header information mState.getWriter().setAuthor (""); mState.getWriter().setDescription (""); - mState.getWriter().setRecordCount (0); + mState.getWriter().setRecordCount ( + mDocument.getData().count (CSMWorld::RecordBase::State_Modified) + + mDocument.getData().count (CSMWorld::RecordBase::State_ModifiedOnly) + + mDocument.getData().count (CSMWorld::RecordBase::State_Deleted)); /// \todo refine dependency list (at least remove redundant dependencies) std::vector dependencies = mDocument.getContentFiles(); diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 7eb96a5c38..1bd818db4c 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -43,6 +43,17 @@ void CSMWorld::Data::appendIds (std::vector& ids, const CollectionB ids.insert (ids.end(), ids2.begin(), ids2.end()); } +int CSMWorld::Data::count (RecordBase::State state, const CollectionBase& collection) +{ + int number = 0; + + for (int i=0; i); @@ -460,6 +471,24 @@ bool CSMWorld::Data::hasId (const std::string& id) const getReferenceables().searchId (id)!=-1; } +int CSMWorld::Data::count (RecordBase::State state) const +{ + return + count (state, mGlobals) + + count (state, mGmsts) + + count (state, mSkills) + + count (state, mClasses) + + count (state, mFactions) + + count (state, mRaces) + + count (state, mSounds) + + count (state, mScripts) + + count (state, mRegions) + + count (state, mBirthsigns) + + count (state, mSpells) + + count (state, mCells) + + count (state, mReferenceables); +} + std::vector CSMWorld::Data::getIds (bool listDeleted) const { std::vector ids; diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index e900bb10fb..ebbafe7118 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -66,6 +66,8 @@ namespace CSMWorld bool listDeleted); ///< Append all IDs from collection to \a ids. + static int count (RecordBase::State state, const CollectionBase& collection); + public: Data(); @@ -151,6 +153,9 @@ namespace CSMWorld /// /// \param listDeleted include deleted record in the list + int count (RecordBase::State state) const; + ///< Return number of top-level records with the given \a state. + signals: void idListChanged(); From 96fd1c35bf3e4296dc0ce012a47f049e6e87ec5d Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 24 Sep 2013 17:17:01 +0200 Subject: [PATCH 054/434] preserve author/descriptin meta data --- apps/opencs/model/doc/document.cpp | 6 ++++++ apps/opencs/model/doc/savingstages.cpp | 5 ++--- apps/opencs/model/world/data.cpp | 23 +++++++++++++++++++++++ apps/opencs/model/world/data.hpp | 10 ++++++++++ 4 files changed, 41 insertions(+), 3 deletions(-) diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index 37294bcd16..f496d32ec3 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -2158,6 +2158,12 @@ CSMDoc::Document::Document (const std::vector& files, load (files.begin(), end, !new_); } + if (new_) + { + mData.setDescription (""); + mData.setAuthor (""); + } + addOptionalGmsts(); addOptionalGlobals(); diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index ee2943ef45..23a88bc17a 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -45,9 +45,8 @@ void CSMDoc::WriteHeaderStage::perform (int stage, std::vector& mes mState.getWriter().setFormat (0); - /// \todo fill in missing header information - mState.getWriter().setAuthor (""); - mState.getWriter().setDescription (""); + mState.getWriter().setAuthor (mDocument.getData().getAuthor()); + mState.getWriter().setDescription (mDocument.getData().getDescription()); mState.getWriter().setRecordCount ( mDocument.getData().count (CSMWorld::RecordBase::State_Modified) + mDocument.getData().count (CSMWorld::RecordBase::State_ModifiedOnly) + diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 1bd818db4c..c6423b76a6 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -395,6 +395,9 @@ void CSMWorld::Data::loadFile (const boost::filesystem::path& path, bool base) reader.open (path.string()); + mAuthor = reader.getAuthor(); + mDescription = reader.getDesc(); + // Note: We do not need to send update signals here, because at this point the model is not connected // to any view. while (reader.hasMoreRecs()) @@ -489,6 +492,26 @@ int CSMWorld::Data::count (RecordBase::State state) const count (state, mReferenceables); } +void CSMWorld::Data::setDescription (const std::string& description) +{ + mDescription = description; +} + +std::string CSMWorld::Data::getDescription() const +{ + return mDescription; +} + +void CSMWorld::Data::setAuthor (const std::string& author) +{ + mAuthor = author; +} + +std::string CSMWorld::Data::getAuthor() const +{ + return mAuthor; +} + std::vector CSMWorld::Data::getIds (bool listDeleted) const { std::vector ids; diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index ebbafe7118..eb6325a257 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -54,6 +54,8 @@ namespace CSMWorld IdCollection mFilters; std::vector mModels; std::map mModelIndex; + std::string mAuthor; + std::string mDescription; // not implemented Data (const Data&); @@ -156,6 +158,14 @@ namespace CSMWorld int count (RecordBase::State state) const; ///< Return number of top-level records with the given \a state. + void setDescription (const std::string& description); + + std::string getDescription() const; + + void setAuthor (const std::string& author); + + std::string getAuthor() const; + signals: void idListChanged(); From 9353f4d14f9b96fffe23b92eaa75a974fb061fb6 Mon Sep 17 00:00:00 2001 From: gus Date: Wed, 25 Sep 2013 18:01:36 +0200 Subject: [PATCH 055/434] WIP AI combat --- apps/openmw/mwmechanics/aicombat.cpp | 87 ++++++++++++++++++++++++++ apps/openmw/mwmechanics/aicombat.hpp | 39 ++++++++++++ apps/openmw/mwmechanics/aisequence.cpp | 71 +++++++++++++-------- apps/openmw/mwmechanics/aisequence.hpp | 3 + 4 files changed, 173 insertions(+), 27 deletions(-) create mode 100644 apps/openmw/mwmechanics/aicombat.cpp create mode 100644 apps/openmw/mwmechanics/aicombat.hpp diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp new file mode 100644 index 0000000000..071aa50e39 --- /dev/null +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -0,0 +1,87 @@ +#include "aicombat.hpp" + +#include "movement.hpp" + +#include "../mwworld/class.hpp" +#include "../mwworld/player.hpp" +#include "../mwworld/timestamp.hpp" +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" + + +namespace MWMechanics +{ + + AiCombat::AiCombat(const std::string &targetId) + :mTargetId(targetId) + { + } + + bool AiCombat::execute (const MWWorld::Ptr& actor) + { + const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();//MWBase::Environment::get().getWorld()->getPtr(mTargetId, false); + + MWMechanics::DrawState_ state = MWWorld::Class::get(actor).getNpcStats(actor).getDrawState(); + if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing) + MWWorld::Class::get(actor).getNpcStats(actor).setDrawState(MWMechanics::DrawState_Weapon); + MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(true); + + ESM::Position pos = actor.getRefData().getPosition(); + const ESM::Pathgrid *pathgrid = + MWBase::Environment::get().getWorld()->getStore().get().search(*actor.getCell()->mCell); + + int cellX = actor.getCell()->mCell->mData.mX; + int cellY = actor.getCell()->mCell->mData.mY; + float xCell = 0; + float yCell = 0; + + if (actor.getCell()->mCell->isExterior()) + { + xCell = actor.getCell()->mCell->mData.mX * ESM::Land::REAL_SIZE; + yCell = actor.getCell()->mCell->mData.mY * ESM::Land::REAL_SIZE; + } + + ESM::Pathgrid::Point dest; + dest.mX = target.getRefData().getPosition().pos[0]; + dest.mY = target.getRefData().getPosition().pos[1]; + dest.mZ = target.getRefData().getPosition().pos[2]; + + ESM::Pathgrid::Point start; + start.mX = pos.pos[0]; + start.mY = pos.pos[1]; + start.mZ = pos.pos[2]; + + std::cout << start.mX << " " << dest.mX << "\n"; + + mPathFinder.buildPath(start, dest, pathgrid, xCell, yCell, true); + + if(mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2])) + { + MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0; + } + + float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); + std::cout << zAngle; + MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false); + MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; + + if(dest.mX - start.mX < 100) + { + //MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(false); + } + + return false; + } + + int AiCombat::getTypeId() const + { + return 2; + } + + AiCombat *MWMechanics::AiCombat::clone() const + { + return new AiCombat(*this); + } +} + diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp new file mode 100644 index 0000000000..3b2ae6fc0b --- /dev/null +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -0,0 +1,39 @@ +#ifndef GAME_MWMECHANICS_AICOMBAT_H +#define GAME_MWMECHANICS_AICOMBAT_H + +#include "aipackage.hpp" + +#include "pathfinding.hpp" + + +#include "..\mwworld\class.hpp" +#include "creaturestats.hpp" +#include "npcstats.hpp" +#include "..\mwbase\environment.hpp" +#include "..\mwbase\world.hpp" +#include "..\mwworld\player.hpp" + +#include "movement.hpp" + +namespace MWMechanics +{ + class AiCombat : public AiPackage + { + public: + AiCombat(const std::string &targetId); + + virtual AiCombat *clone() const; + + virtual bool execute (const MWWorld::Ptr& actor); + ///< \return Package completed? + + virtual int getTypeId() const; + + private: + std::string mTargetId; + + PathFinder mPathFinder; + }; +} + +#endif \ No newline at end of file diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 79ecf1586c..d02f03e18d 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -8,6 +8,7 @@ #include "aitravel.hpp" #include "aifollow.hpp" #include "aiactivate.hpp" +#include "aicombat.hpp" #include "..\mwworld\class.hpp" #include "creaturestats.hpp" @@ -21,9 +22,11 @@ void MWMechanics::AiSequence::copy (const AiSequence& sequence) for (std::list::const_iterator iter (sequence.mPackages.begin()); iter!=sequence.mPackages.end(); ++iter) mPackages.push_back ((*iter)->clone()); + mCombat = sequence.mCombat; + mCombatPackage = sequence.mCombatPackage; } -MWMechanics::AiSequence::AiSequence() : mDone (false) {} +MWMechanics::AiSequence::AiSequence() : mDone (false), mCombat (false), mCombatPackage (0) {} MWMechanics::AiSequence::AiSequence (const AiSequence& sequence) : mDone (false) { @@ -61,16 +64,27 @@ bool MWMechanics::AiSequence::isPackageDone() const void MWMechanics::AiSequence::execute (const MWWorld::Ptr& actor) { - /*if(actor != MWBase::Environment::get().getWorld()->getPlayer().getPlayer()) + if(actor != MWBase::Environment::get().getWorld()->getPlayer().getPlayer()) { - MWMechanics::DrawState_ state = MWWorld::Class::get(actor).getNpcStats(actor).getDrawState(); - if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing) - MWWorld::Class::get(actor).getNpcStats(actor).setDrawState(MWMechanics::DrawState_Weapon); - MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(true); + if(mCombat) + { + //mCombatPackage->execute(actor); + } + else + { + //mCombat = true; + //mCombatPackage = new AiCombat("player"); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); - ESM::Position pos = actor.getRefData().getPosition(); - const ESM::Pathgrid *pathgrid = + /*if(actor != MWBase::Environment::get().getWorld()->getPlayer().getPlayer()) + { + MWMechanics::DrawState_ state = MWWorld::Class::get(actor).getNpcStats(actor).getDrawState(); + if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing) + MWWorld::Class::get(actor).getNpcStats(actor).setDrawState(MWMechanics::DrawState_Weapon); + MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(true); + + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + ESM::Position pos = actor.getRefData().getPosition(); + const ESM::Pathgrid *pathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*actor.getCell()->mCell); int cellX = actor.getCell()->mCell->mData.mX; @@ -80,8 +94,8 @@ void MWMechanics::AiSequence::execute (const MWWorld::Ptr& actor) if (actor.getCell()->mCell->isExterior()) { - xCell = actor.getCell()->mCell->mData.mX * ESM::Land::REAL_SIZE; - yCell = actor.getCell()->mCell->mData.mY * ESM::Land::REAL_SIZE; + xCell = actor.getCell()->mCell->mData.mX * ESM::Land::REAL_SIZE; + yCell = actor.getCell()->mCell->mData.mY * ESM::Land::REAL_SIZE; } ESM::Pathgrid::Point dest; @@ -96,24 +110,26 @@ void MWMechanics::AiSequence::execute (const MWWorld::Ptr& actor) PathFinder mPathFinder; mPathFinder.buildPath(start, dest, pathgrid, xCell, yCell, true); - float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); - MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false); - MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; + float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); + MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false); + MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; - if(dest.mX - start.mX < 100) - { + if(dest.mX - start.mX < 100) + { MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(false); + } + }*/ + if (!mPackages.empty()) + { + if (mPackages.front()->execute (actor)) + { + mPackages.erase (mPackages.begin()); + mDone = true; + } + else + mDone = false; + } } - }*/ - if (!mPackages.empty()) - { - if (mPackages.front()->execute (actor)) - { - mPackages.erase (mPackages.begin()); - mDone = true; - } - else - mDone = false; } } @@ -121,7 +137,8 @@ void MWMechanics::AiSequence::clear() { for (std::list::const_iterator iter (mPackages.begin()); iter!=mPackages.end(); ++iter) delete *iter; - + + if(mCombatPackage) delete mCombatPackage; mPackages.clear(); } diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index 9f70daeb83..37084a1e5b 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -18,6 +18,9 @@ namespace MWMechanics class AiSequence { std::list mPackages; + AiPackage* mCombatPackage; + bool mCombat; + bool mDone; void copy (const AiSequence& sequence); From 5779f799ab064d72cf9641aaccdd8107d50c098b Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 27 Sep 2013 11:36:06 +0200 Subject: [PATCH 056/434] create project file when saving content file --- apps/opencs/editor.cpp | 3 +- apps/opencs/editor.hpp | 2 +- apps/opencs/model/doc/document.cpp | 5 +- apps/opencs/model/doc/document.hpp | 5 +- apps/opencs/model/doc/documentmanager.cpp | 15 +++++- apps/opencs/model/doc/documentmanager.hpp | 4 +- apps/opencs/model/doc/saving.cpp | 16 +++++-- apps/opencs/model/doc/saving.hpp | 4 +- apps/opencs/model/doc/savingstages.cpp | 58 ++++++++++++++--------- apps/opencs/model/doc/savingstages.hpp | 8 +++- apps/opencs/model/doc/savingstate.cpp | 19 ++++++-- apps/opencs/model/doc/savingstate.hpp | 10 +++- components/esm/esmwriter.cpp | 5 ++ components/esm/esmwriter.hpp | 2 + components/esm/loadtes3.cpp | 1 + 15 files changed, 113 insertions(+), 44 deletions(-) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 800f3984e9..ead4d2a982 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -10,7 +10,8 @@ #include "model/world/data.hpp" -CS::Editor::Editor() : mViewManager (mDocumentManager) +CS::Editor::Editor() +: mDocumentManager (mCfgMgr.getUserPath() / "projects"), mViewManager (mDocumentManager) { mIpcServerName = "org.openmw.OpenCS"; diff --git a/apps/opencs/editor.hpp b/apps/opencs/editor.hpp index abf9496e4b..16f6b9516c 100644 --- a/apps/opencs/editor.hpp +++ b/apps/opencs/editor.hpp @@ -26,6 +26,7 @@ namespace CS { Q_OBJECT + Files::ConfigurationManager mCfgMgr; CSMSettings::UserSettings mUserSettings; CSMDoc::DocumentManager mDocumentManager; CSVDoc::ViewManager mViewManager; @@ -34,7 +35,6 @@ namespace CS CSVSettings::UserSettingsDialog mSettings; CSVDoc::FileDialog mFileDialog; - Files::ConfigurationManager mCfgMgr; boost::filesystem::path mLocal; void setupDataFiles(); diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index f496d32ec3..f9aa7dfc0f 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -2140,8 +2140,9 @@ void CSMDoc::Document::createBase() } CSMDoc::Document::Document (const std::vector& files, - const boost::filesystem::path& savePath, bool new_) -: mSavePath (savePath), mContentFiles (files), mTools (mData), mSaving (*this) + const boost::filesystem::path& savePath, bool new_, + const boost::filesystem::path& projectPath) +: mSavePath (savePath), mContentFiles (files), mTools (mData), mSaving (*this, projectPath) { if (files.empty()) throw std::runtime_error ("Empty content file sequence"); diff --git a/apps/opencs/model/doc/document.hpp b/apps/opencs/model/doc/document.hpp index b1c6a02731..979b47734a 100644 --- a/apps/opencs/model/doc/document.hpp +++ b/apps/opencs/model/doc/document.hpp @@ -65,7 +65,10 @@ namespace CSMDoc public: Document (const std::vector& files, - const boost::filesystem::path& savePath, bool new_); + const boost::filesystem::path& savePath, bool new_, + const boost::filesystem::path& projectPath); + ///< \param projectPath Location of file that can be used to store additional data for + /// this project. ~Document(); diff --git a/apps/opencs/model/doc/documentmanager.cpp b/apps/opencs/model/doc/documentmanager.cpp index b079109eae..1978c0e536 100644 --- a/apps/opencs/model/doc/documentmanager.cpp +++ b/apps/opencs/model/doc/documentmanager.cpp @@ -4,9 +4,16 @@ #include #include +#include + #include "document.hpp" -CSMDoc::DocumentManager::DocumentManager() {} +CSMDoc::DocumentManager::DocumentManager (const boost::filesystem::path& projectPath) +: mProjectPath (projectPath) +{ + if (!boost::filesystem::is_directory (mProjectPath)) + boost::filesystem::create_directories (mProjectPath); +} CSMDoc::DocumentManager::~DocumentManager() { @@ -17,7 +24,11 @@ CSMDoc::DocumentManager::~DocumentManager() CSMDoc::Document *CSMDoc::DocumentManager::addDocument (const std::vector& files, const boost::filesystem::path& savePath, bool new_) { - Document *document = new Document (files, savePath, new_); + boost::filesystem::path projectFile (mProjectPath); + + projectFile /= savePath.filename().string() + ".project"; + + Document *document = new Document (files, savePath, new_, projectFile); mDocuments.push_back (document); diff --git a/apps/opencs/model/doc/documentmanager.hpp b/apps/opencs/model/doc/documentmanager.hpp index dfded8d5c8..622a135a58 100644 --- a/apps/opencs/model/doc/documentmanager.hpp +++ b/apps/opencs/model/doc/documentmanager.hpp @@ -13,13 +13,15 @@ namespace CSMDoc class DocumentManager { std::vector mDocuments; + boost::filesystem::path mProjectPath; DocumentManager (const DocumentManager&); DocumentManager& operator= (const DocumentManager&); public: - DocumentManager(); + DocumentManager (const boost::filesystem::path& projectPath); + ///< \param projectPath Directory where additional per-project data will be stored. ~DocumentManager(); diff --git a/apps/opencs/model/doc/saving.cpp b/apps/opencs/model/doc/saving.cpp index 98af661fdf..adcfca5768 100644 --- a/apps/opencs/model/doc/saving.cpp +++ b/apps/opencs/model/doc/saving.cpp @@ -8,12 +8,20 @@ #include "savingstages.hpp" #include "document.hpp" -CSMDoc::Saving::Saving (Document& document) -: Operation (State_Saving, true, true), mDocument (document), mState (*this) +CSMDoc::Saving::Saving (Document& document, const boost::filesystem::path& projectPath) +: Operation (State_Saving, true, true), mDocument (document), mState (*this, projectPath) { - appendStage (new OpenSaveStage (mDocument, mState)); + // save project file + appendStage (new OpenSaveStage (mDocument, mState, true)); - appendStage (new WriteHeaderStage (mDocument, mState)); + appendStage (new WriteHeaderStage (mDocument, mState, true)); + + appendStage (new CloseSaveStage (mState)); + + // save content file + appendStage (new OpenSaveStage (mDocument, mState, false)); + + appendStage (new WriteHeaderStage (mDocument, mState, false)); appendStage (new WriteCollectionStage > (mDocument.getData().getGlobals(), mState)); diff --git a/apps/opencs/model/doc/saving.hpp b/apps/opencs/model/doc/saving.hpp index b89ba5f6db..cd1bbef980 100644 --- a/apps/opencs/model/doc/saving.hpp +++ b/apps/opencs/model/doc/saving.hpp @@ -1,6 +1,8 @@ #ifndef CSM_DOC_SAVING_H #define CSM_DOC_SAVING_H +#include + #include "operation.hpp" #include "savingstate.hpp" @@ -17,7 +19,7 @@ namespace CSMDoc public: - Saving (Document& document); + Saving (Document& document, const boost::filesystem::path& projectPath); }; } diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index 23a88bc17a..d48e220123 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -10,8 +10,8 @@ #include "document.hpp" #include "savingstate.hpp" -CSMDoc::OpenSaveStage::OpenSaveStage (Document& document, SavingState& state) -: mDocument (document), mState (state) +CSMDoc::OpenSaveStage::OpenSaveStage (Document& document, SavingState& state, bool projectFile) +: mDocument (document), mState (state), mProjectFile (projectFile) {} int CSMDoc::OpenSaveStage::setup() @@ -21,17 +21,17 @@ int CSMDoc::OpenSaveStage::setup() void CSMDoc::OpenSaveStage::perform (int stage, std::vector& messages) { - mState.start (mDocument); + mState.start (mDocument, mProjectFile); - mState.getStream().open (mState.getTmpPath().string().c_str()); + mState.getStream().open ((mProjectFile ? mState.getPath() : mState.getTmpPath()).string().c_str()); if (!mState.getStream().is_open()) throw std::runtime_error ("failed to open stream for saving"); } -CSMDoc::WriteHeaderStage::WriteHeaderStage (Document& document, SavingState& state) -: mDocument (document), mState (state) +CSMDoc::WriteHeaderStage::WriteHeaderStage (Document& document, SavingState& state, bool simple) +: mDocument (document), mState (state), mSimple (simple) {} int CSMDoc::WriteHeaderStage::setup() @@ -43,26 +43,38 @@ void CSMDoc::WriteHeaderStage::perform (int stage, std::vector& mes { mState.getWriter().setVersion(); + mState.getWriter().clearMaster(); + mState.getWriter().setFormat (0); - mState.getWriter().setAuthor (mDocument.getData().getAuthor()); - mState.getWriter().setDescription (mDocument.getData().getDescription()); - mState.getWriter().setRecordCount ( - mDocument.getData().count (CSMWorld::RecordBase::State_Modified) + - mDocument.getData().count (CSMWorld::RecordBase::State_ModifiedOnly) + - mDocument.getData().count (CSMWorld::RecordBase::State_Deleted)); - - /// \todo refine dependency list (at least remove redundant dependencies) - std::vector dependencies = mDocument.getContentFiles(); - std::vector::const_iterator end (--dependencies.end()); - - for (std::vector::const_iterator iter (dependencies.begin()); - iter!=end; ++iter) + if (mSimple) { - std::string name = iter->filename().string(); - uint64_t size = boost::filesystem::file_size (*iter); + mState.getWriter().setAuthor (""); + mState.getWriter().setDescription (""); + mState.getWriter().setRecordCount (0); - mState.getWriter().addMaster (name, size); + } + else + { + mState.getWriter().setAuthor (mDocument.getData().getAuthor()); + mState.getWriter().setDescription (mDocument.getData().getDescription()); + mState.getWriter().setRecordCount ( + mDocument.getData().count (CSMWorld::RecordBase::State_Modified) + + mDocument.getData().count (CSMWorld::RecordBase::State_ModifiedOnly) + + mDocument.getData().count (CSMWorld::RecordBase::State_Deleted)); + + /// \todo refine dependency list (at least remove redundant dependencies) + std::vector dependencies = mDocument.getContentFiles(); + std::vector::const_iterator end (--dependencies.end()); + + for (std::vector::const_iterator iter (dependencies.begin()); + iter!=end; ++iter) + { + std::string name = iter->filename().string(); + uint64_t size = boost::filesystem::file_size (*iter); + + mState.getWriter().addMaster (name, size); + } } mState.getWriter().save (mState.getStream()); @@ -121,7 +133,7 @@ void CSMDoc::FinalSavingStage::perform (int stage, std::vector& mes if (boost::filesystem::exists (mState.getTmpPath())) boost::filesystem::remove (mState.getTmpPath()); } - else + else if (!mState.isProjectFile()) { if (boost::filesystem::exists (mState.getPath())) boost::filesystem::remove (mState.getPath()); diff --git a/apps/opencs/model/doc/savingstages.hpp b/apps/opencs/model/doc/savingstages.hpp index 2d32f38cbd..367431fc12 100644 --- a/apps/opencs/model/doc/savingstages.hpp +++ b/apps/opencs/model/doc/savingstages.hpp @@ -16,10 +16,12 @@ namespace CSMDoc { Document& mDocument; SavingState& mState; + bool mProjectFile; public: - OpenSaveStage (Document& document, SavingState& state); + OpenSaveStage (Document& document, SavingState& state, bool projectFile); + ///< \param projectFile Saving the project file instead of the content file. virtual int setup(); ///< \return number of steps @@ -32,10 +34,12 @@ namespace CSMDoc { Document& mDocument; SavingState& mState; + bool mSimple; public: - WriteHeaderStage (Document& document, SavingState& state); + WriteHeaderStage (Document& document, SavingState& state, bool simple); + ///< \param simple Simplified header (used for project files). virtual int setup(); ///< \return number of steps diff --git a/apps/opencs/model/doc/savingstate.cpp b/apps/opencs/model/doc/savingstate.cpp index a49a0699b2..4a1abb8883 100644 --- a/apps/opencs/model/doc/savingstate.cpp +++ b/apps/opencs/model/doc/savingstate.cpp @@ -4,10 +4,11 @@ #include "operation.hpp" #include "document.hpp" -CSMDoc::SavingState::SavingState (Operation& operation) +CSMDoc::SavingState::SavingState (Operation& operation, const boost::filesystem::path& projectPath) : mOperation (operation), /// \todo set encoding properly, once config implementation has been fixed. - mEncoder (ToUTF8::calculateEncoding ("win1252")) + mEncoder (ToUTF8::calculateEncoding ("win1252")), + mProjectPath (projectPath), mProjectFile (false) { mWriter.setEncoder (&mEncoder); } @@ -17,14 +18,19 @@ bool CSMDoc::SavingState::hasError() const return mOperation.hasError(); } -void CSMDoc::SavingState::start (Document& document) +void CSMDoc::SavingState::start (Document& document, bool project) { + mProjectFile = project; + if (mStream.is_open()) mStream.close(); mStream.clear(); - mPath = document.getSavePath(); + if (project) + mPath = mProjectPath; + else + mPath = document.getSavePath(); boost::filesystem::path file (mPath.filename().string() + ".tmp"); @@ -51,4 +57,9 @@ std::ofstream& CSMDoc::SavingState::getStream() ESM::ESMWriter& CSMDoc::SavingState::getWriter() { return mWriter; +} + +bool CSMDoc::SavingState::isProjectFile() const +{ + return mProjectFile; } \ No newline at end of file diff --git a/apps/opencs/model/doc/savingstate.hpp b/apps/opencs/model/doc/savingstate.hpp index 3f42b4653d..8cf7883e50 100644 --- a/apps/opencs/model/doc/savingstate.hpp +++ b/apps/opencs/model/doc/savingstate.hpp @@ -20,14 +20,17 @@ namespace CSMDoc ToUTF8::Utf8Encoder mEncoder; std::ofstream mStream; ESM::ESMWriter mWriter; + boost::filesystem::path mProjectPath; + bool mProjectFile; public: - SavingState (Operation& operation); + SavingState (Operation& operation, const boost::filesystem::path& projectPath); bool hasError() const; - void start (Document& document); + void start (Document& document, bool project); + ///< \param project Save project file instead of content file. const boost::filesystem::path& getPath() const; @@ -36,6 +39,9 @@ namespace CSMDoc std::ofstream& getStream(); ESM::ESMWriter& getWriter(); + + bool isProjectFile() const; + ///< Currently saving project file? (instead of content file) }; diff --git a/components/esm/esmwriter.cpp b/components/esm/esmwriter.cpp index 95ad44811d..f39aa2b898 100644 --- a/components/esm/esmwriter.cpp +++ b/components/esm/esmwriter.cpp @@ -38,6 +38,11 @@ namespace ESM mHeader.mFormat = format; } + void ESMWriter::clearMaster() + { + mHeader.mMaster.clear(); + } + void ESMWriter::addMaster(const std::string& name, uint64_t size) { Header::MasterData d; diff --git a/components/esm/esmwriter.hpp b/components/esm/esmwriter.hpp index fc64c4a137..104f97f909 100644 --- a/components/esm/esmwriter.hpp +++ b/components/esm/esmwriter.hpp @@ -32,6 +32,8 @@ class ESMWriter void setRecordCount (int count); void setFormat (int format); + void clearMaster(); + void addMaster(const std::string& name, uint64_t size); void save(const std::string& file); diff --git a/components/esm/loadtes3.cpp b/components/esm/loadtes3.cpp index a86c9b6f4f..87a8d1d57e 100644 --- a/components/esm/loadtes3.cpp +++ b/components/esm/loadtes3.cpp @@ -14,6 +14,7 @@ void ESM::Header::blank() mData.desc.assign (""); mData.records = 0; mFormat = CurrentFormat; + mMaster.clear(); } void ESM::Header::load (ESMReader &esm) From 4ea5191d7d4c0ec38941f2e3731b4ee4a6e38925 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 27 Sep 2013 13:17:04 +0200 Subject: [PATCH 057/434] fixed write function for ESM variant type --- components/esm/variantimp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esm/variantimp.cpp b/components/esm/variantimp.cpp index 160402aa4b..1bacdc0770 100644 --- a/components/esm/variantimp.cpp +++ b/components/esm/variantimp.cpp @@ -193,7 +193,7 @@ void ESM::VariantIntegerData::write (ESMWriter& esm, Variant::Format format, Var } else if (format==Variant::Format_Gmst || format==Variant::Format_Info) { - if (type==VT_Int) + if (type!=VT_Int) { std::ostringstream stream; stream From 23095ec3ec5f934d74895b99dd5ad010cb42c4ce Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 27 Sep 2013 13:54:21 +0200 Subject: [PATCH 058/434] added missing scope column to filter table --- apps/opencs/model/world/columnimp.hpp | 30 +++++++++++++++++++++++++++ apps/opencs/model/world/data.cpp | 1 + 2 files changed, 31 insertions(+) diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index 1a2bf9df13..f50212e569 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -1216,6 +1216,36 @@ namespace CSMWorld return true; } }; + + template + struct ScopeColumn : public Column + { + ScopeColumn() + : Column (Columns::ColumnId_Scope, ColumnBase::Display_Integer, 0) + {} + + virtual QVariant get (const Record& record) const + { + return static_cast (record.get().mScope); + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + record2.mScope = static_cast (data.toInt()); + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + + virtual bool isUserEditable() const + { + return false; + } + }; } #endif diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index c6423b76a6..a5e98fc60c 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -182,6 +182,7 @@ CSMWorld::Data::Data() : mRefs (mCells) mFilters.addColumn (new RecordStateColumn); mFilters.addColumn (new FilterColumn); mFilters.addColumn (new DescriptionColumn); + mFilters.addColumn (new ScopeColumn); addModel (new IdTable (&mGlobals), UniversalId::Type_Globals, UniversalId::Type_Global); addModel (new IdTable (&mGmsts), UniversalId::Type_Gmsts, UniversalId::Type_Gmst); From 6f2c418a5cdd8d96b0d1c0afa859d8a06086349a Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 27 Sep 2013 13:54:51 +0200 Subject: [PATCH 059/434] (slightly) improved error reporting during save operations --- apps/opencs/model/doc/document.cpp | 7 +++++++ apps/opencs/model/doc/document.hpp | 2 ++ apps/opencs/model/doc/operation.cpp | 3 ++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index f9aa7dfc0f..68e164c797 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -2175,6 +2175,8 @@ CSMDoc::Document::Document (const std::vector& files, connect (&mSaving, SIGNAL (progress (int, int, int)), this, SLOT (progress (int, int, int))); connect (&mSaving, SIGNAL (done (int)), this, SLOT (operationDone (int))); + connect (&mSaving, SIGNAL (reportMessage (const QString&, int)), + this, SLOT (reportMessage (const QString&, int))); } CSMDoc::Document::~Document() @@ -2243,6 +2245,11 @@ void CSMDoc::Document::modificationStateChanged (bool clean) emit stateChanged (getState(), this); } +void CSMDoc::Document::reportMessage (const QString& message, int type) +{ + /// \todo find a better way to get these messages to the user. + std::cout << message.toUtf8().constData() << std::endl; +} void CSMDoc::Document::operationDone (int type) { diff --git a/apps/opencs/model/doc/document.hpp b/apps/opencs/model/doc/document.hpp index 979b47734a..7843cfbfe9 100644 --- a/apps/opencs/model/doc/document.hpp +++ b/apps/opencs/model/doc/document.hpp @@ -105,6 +105,8 @@ namespace CSMDoc void modificationStateChanged (bool clean); + void reportMessage (const QString& message, int type); + void operationDone (int type); public slots: diff --git a/apps/opencs/model/doc/operation.cpp b/apps/opencs/model/doc/operation.cpp index 8af5a2c0dc..d29cc2631b 100644 --- a/apps/opencs/model/doc/operation.cpp +++ b/apps/opencs/model/doc/operation.cpp @@ -95,8 +95,9 @@ void CSMDoc::Operation::executeStage() { mCurrentStage->first->perform (mCurrentStep++, messages); } - catch (const std::exception&) + catch (const std::exception& e) { + emit reportMessage (e.what(), mType); abort(); } From 31346dde58090fed76333272d5593ef24eae96e4 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 27 Sep 2013 13:55:28 +0200 Subject: [PATCH 060/434] fixed uninitialise scope value for filters --- apps/opencs/view/filter/filtercreator.cpp | 16 +++++++++++++++- apps/opencs/view/filter/filtercreator.hpp | 2 ++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/apps/opencs/view/filter/filtercreator.cpp b/apps/opencs/view/filter/filtercreator.cpp index 47925ea57a..640c9fe785 100644 --- a/apps/opencs/view/filter/filtercreator.cpp +++ b/apps/opencs/view/filter/filtercreator.cpp @@ -6,6 +6,11 @@ #include "../../model/filter/filter.hpp" +#include "../../model/world/data.hpp" +#include "../../model/world/commands.hpp" +#include "../../model/world/columns.hpp" +#include "../../model/world/idtable.hpp" + std::string CSVFilter::FilterCreator::getNamespace() const { switch (mScope->currentIndex()) @@ -28,6 +33,15 @@ std::string CSVFilter::FilterCreator::getId() const return getNamespace() + GenericCreator::getId(); } +void CSVFilter::FilterCreator::configureCreateCommand (CSMWorld::CreateCommand& command) const +{ + int index = + dynamic_cast (*getData().getTableModel (getCollectionId())). + findColumnIndex (CSMWorld::Columns::ColumnId_Scope); + + command.addValue (index, mScope->currentIndex()); +} + CSVFilter::FilterCreator::FilterCreator (CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id) : GenericCreator (data, undoStack, id) @@ -39,7 +53,7 @@ CSVFilter::FilterCreator::FilterCreator (CSMWorld::Data& data, QUndoStack& undoS mScope->addItem ("Project"); mScope->addItem ("Session"); - /// \ŧodo re-enable for OpenMW 1.1 + /// \todo re-enable for OpenMW 1.1 // mScope->addItem ("Content"); connect (mScope, SIGNAL (currentIndexChanged (int)), this, SLOT (setScope (int))); diff --git a/apps/opencs/view/filter/filtercreator.hpp b/apps/opencs/view/filter/filtercreator.hpp index 82d38d22c7..437d01c8da 100644 --- a/apps/opencs/view/filter/filtercreator.hpp +++ b/apps/opencs/view/filter/filtercreator.hpp @@ -25,6 +25,8 @@ namespace CSVFilter virtual std::string getId() const; + virtual void configureCreateCommand (CSMWorld::CreateCommand& command) const; + public: FilterCreator (CSMWorld::Data& data, QUndoStack& undoStack, From 6ac4dedfbe7f88fb353cfdab0128cea612a85e38 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 27 Sep 2013 13:56:23 +0200 Subject: [PATCH 061/434] added missing column enum --- apps/opencs/model/world/columns.cpp | 1 + apps/opencs/model/world/columns.hpp | 1 + 2 files changed, 2 insertions(+) diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index 5616a4a481..06649b01bf 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -147,6 +147,7 @@ namespace CSMWorld { ColumnId_Magical, "Magical" }, { ColumnId_Silver, "Silver" }, { ColumnId_Filter, "Filter" }, + { ColumnId_Scope, "Scope", }, { ColumnId_UseValue1, "Use value 1" }, { ColumnId_UseValue2, "Use value 2" }, diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index 69b20583ae..bf1387067d 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -140,6 +140,7 @@ namespace CSMWorld ColumnId_Magical = 107, ColumnId_Silver = 108, ColumnId_Filter = 109, + ColumnId_Scope = 110, // Allocated to a separate value range, so we don't get a collision should we ever need // to extend the number of use values. From baae548106332e373cb16e3855ac588f16c1b7c4 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 27 Sep 2013 13:56:40 +0200 Subject: [PATCH 062/434] added project scope filter saving --- apps/opencs/model/doc/saving.cpp | 2 ++ apps/opencs/model/doc/savingstages.cpp | 18 +++++++++++++++++- apps/opencs/model/doc/savingstages.hpp | 17 +++++++++++++++++ components/esm/defs.hpp | 6 +++++- components/esm/filter.cpp | 3 +++ components/esm/filter.hpp | 2 ++ 6 files changed, 46 insertions(+), 2 deletions(-) diff --git a/apps/opencs/model/doc/saving.cpp b/apps/opencs/model/doc/saving.cpp index adcfca5768..2b0056e721 100644 --- a/apps/opencs/model/doc/saving.cpp +++ b/apps/opencs/model/doc/saving.cpp @@ -16,6 +16,8 @@ CSMDoc::Saving::Saving (Document& document, const boost::filesystem::path& proje appendStage (new WriteHeaderStage (mDocument, mState, true)); + appendStage (new WriteFilterStage (mDocument, mState, CSMFilter::Filter::Scope_Project)); + appendStage (new CloseSaveStage (mState)); // save content file diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index d48e220123..d68c723179 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -52,7 +52,6 @@ void CSMDoc::WriteHeaderStage::perform (int stage, std::vector& mes mState.getWriter().setAuthor (""); mState.getWriter().setDescription (""); mState.getWriter().setRecordCount (0); - } else { @@ -96,6 +95,23 @@ void CSMDoc::WriteRefIdCollectionStage::perform (int stage, std::vector > (document.getData().getFilters(), + state), + mDocument (document), mScope (scope) +{} + +void CSMDoc::WriteFilterStage::perform (int stage, std::vector& messages) +{ + const CSMWorld::Record& record = + mDocument.getData().getFilters().getRecord (stage); + + if (record.get().mScope==mScope) + WriteCollectionStage >::perform (stage, messages); +} + + CSMDoc::CloseSaveStage::CloseSaveStage (SavingState& state) : mState (state) {} diff --git a/apps/opencs/model/doc/savingstages.hpp b/apps/opencs/model/doc/savingstages.hpp index 367431fc12..ff94116fdb 100644 --- a/apps/opencs/model/doc/savingstages.hpp +++ b/apps/opencs/model/doc/savingstages.hpp @@ -6,6 +6,9 @@ #include "savingstate.hpp" #include "../world/record.hpp" +#include "../world/idcollection.hpp" + +#include "../filter/filter.hpp" namespace CSMDoc { @@ -120,6 +123,20 @@ namespace CSMDoc }; + class WriteFilterStage : public WriteCollectionStage > + { + Document& mDocument; + CSMFilter::Filter::Scope mScope; + + public: + + WriteFilterStage (Document& document, SavingState& state, CSMFilter::Filter::Scope scope); + + virtual void perform (int stage, std::vector& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; + + class CloseSaveStage : public Stage { SavingState& mState; diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index bd86f9ba03..dd7ebfe932 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -36,6 +36,7 @@ struct Position enum RecNameInts { + // format 0 / legacy REC_ACTI = 0x49544341, REC_ALCH = 0x48434c41, REC_APPA = 0x41505041, @@ -80,7 +81,10 @@ enum RecNameInts REC_SPEL = 0x4c455053, REC_SSCR = 0x52435353, REC_STAT = 0x54415453, - REC_WEAP = 0x50414557 + REC_WEAP = 0x50414557, + + // format 1 + REC_FILT = 0x544C4946 }; } diff --git a/components/esm/filter.cpp b/components/esm/filter.cpp index 96cc19d43d..a80427bbed 100644 --- a/components/esm/filter.cpp +++ b/components/esm/filter.cpp @@ -3,6 +3,9 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include "defs.hpp" + +unsigned int ESM::Filter::sRecordId = REC_FILT; void ESM::Filter::load (ESMReader& esm) { diff --git a/components/esm/filter.hpp b/components/esm/filter.hpp index a44d1b1980..bc3dd7bdcb 100644 --- a/components/esm/filter.hpp +++ b/components/esm/filter.hpp @@ -10,6 +10,8 @@ namespace ESM struct Filter { + static unsigned int sRecordId; + std::string mId; std::string mDescription; From 62148b324702158d14366f214b56aa32984d92dc Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 27 Sep 2013 15:04:09 +0200 Subject: [PATCH 063/434] moved implementation of searchColumnIndex and findColumnIndex functions from IdTable to CollectionBase --- apps/opencs/model/world/collectionbase.cpp | 25 ++++++++++++++++++++++ apps/opencs/model/world/collectionbase.hpp | 8 +++++++ apps/opencs/model/world/idtable.cpp | 15 ++----------- 3 files changed, 35 insertions(+), 13 deletions(-) diff --git a/apps/opencs/model/world/collectionbase.cpp b/apps/opencs/model/world/collectionbase.cpp index 932ea27b58..241f198cb2 100644 --- a/apps/opencs/model/world/collectionbase.cpp +++ b/apps/opencs/model/world/collectionbase.cpp @@ -1,6 +1,31 @@ #include "collectionbase.hpp" +#include + +#include "columnbase.hpp" + CSMWorld::CollectionBase::CollectionBase() {} CSMWorld::CollectionBase::~CollectionBase() {} + +int CSMWorld::CollectionBase::searchColumnIndex (Columns::ColumnId id) const +{ + int columns = getColumns(); + + for (int i=0; i #include "universalid.hpp" +#include "columns.hpp" class QVariant; @@ -83,6 +84,13 @@ namespace CSMWorld ///< Return a sorted collection of all IDs /// /// \param listDeleted include deleted record in the list + + int searchColumnIndex (Columns::ColumnId id) const; + ///< Return index of column with the given \a id. If no such column exists, -1 is returned. + + int findColumnIndex (Columns::ColumnId id) const; + ///< Return index of column with the given \a id. If no such column exists, an exception is + /// thrown. }; } diff --git a/apps/opencs/model/world/idtable.cpp b/apps/opencs/model/world/idtable.cpp index baaf75289c..b7b1a9db05 100644 --- a/apps/opencs/model/world/idtable.cpp +++ b/apps/opencs/model/world/idtable.cpp @@ -161,21 +161,10 @@ const CSMWorld::RecordBase& CSMWorld::IdTable::getRecord (const std::string& id) int CSMWorld::IdTable::searchColumnIndex (Columns::ColumnId id) const { - int columns = mIdCollection->getColumns(); - - for (int i=0; igetColumn (i).mColumnId==id) - return i; - - return -1; + return mIdCollection->searchColumnIndex (id); } int CSMWorld::IdTable::findColumnIndex (Columns::ColumnId id) const { - int index = searchColumnIndex (id); - - if (index==-1) - throw std::logic_error ("invalid column index"); - - return index; + return mIdCollection->findColumnIndex (id); } \ No newline at end of file From e7c48cbe5805afa378dfd8e1db90357e8e7d8ab4 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 27 Sep 2013 15:04:30 +0200 Subject: [PATCH 064/434] load project files --- apps/opencs/model/doc/document.cpp | 19 +++++++++++++++++-- apps/opencs/model/world/data.cpp | 15 ++++++++++++++- apps/opencs/model/world/data.hpp | 4 +++- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index 68e164c797..7f609f9f7e 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -1,6 +1,9 @@ #include "document.hpp" + #include +#include + void CSMDoc::Document::load (const std::vector::const_iterator& begin, const std::vector::const_iterator& end, bool lastAsModified) { @@ -12,10 +15,10 @@ void CSMDoc::Document::load (const std::vector::const_i --end2; for (std::vector::const_iterator iter (begin); iter!=end2; ++iter) - getData().loadFile (*iter, true); + getData().loadFile (*iter, true, false); if (lastAsModified) - getData().loadFile (*end2, false); + getData().loadFile (*end2, false, false); } void CSMDoc::Document::addGmsts() @@ -2164,6 +2167,18 @@ CSMDoc::Document::Document (const std::vector& files, mData.setDescription (""); mData.setAuthor (""); } +/// \todo un-outcomment the else, once loading an existing content file works properly again. +// else + { + if (boost::filesystem::exists (projectPath)) + { + getData().loadFile (projectPath, false, true); + } + else + { + /// \todo create new project file with default filters + } + } addOptionalGmsts(); addOptionalGlobals(); diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index a5e98fc60c..9227a5965d 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -386,7 +386,7 @@ void CSMWorld::Data::merge() mGlobals.merge(); } -void CSMWorld::Data::loadFile (const boost::filesystem::path& path, bool base) +void CSMWorld::Data::loadFile (const boost::filesystem::path& path, bool base, bool project) { ESM::ESMReader reader; @@ -449,6 +449,19 @@ void CSMWorld::Data::loadFile (const boost::filesystem::path& path, bool base) case ESM::REC_STAT: mReferenceables.load (reader, base, UniversalId::Type_Static); break; case ESM::REC_WEAP: mReferenceables.load (reader, base, UniversalId::Type_Weapon); break; + case ESM::REC_FILT: + + if (project) + { + mFilters.load (reader, base); + mFilters.setData (mFilters.getSize()-1, + mFilters.findColumnIndex (CSMWorld::Columns::ColumnId_Scope), + static_cast (CSMFilter::Filter::Scope_Project)); + break; + } + + // fall through (filter record in a content file is an error with format 0) + default: /// \todo throw an exception instead, once all records are implemented diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index eb6325a257..5d7fbd2916 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -145,8 +145,10 @@ namespace CSMWorld void merge(); ///< Merge modified into base. - void loadFile (const boost::filesystem::path& path, bool base); + void loadFile (const boost::filesystem::path& path, bool base, bool project); ///< Merging content of a file into base or modified. + /// + /// \param project load project file instead of content file bool hasId (const std::string& id) const; From 6143ec33e0df352cdae04366c92ffa2ab977feb3 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 27 Sep 2013 15:24:58 +0200 Subject: [PATCH 065/434] giving Documents direct access to ConfigurationManager --- apps/opencs/editor.cpp | 2 +- apps/opencs/model/doc/document.cpp | 19 +++++++++++++------ apps/opencs/model/doc/document.hpp | 14 +++++++++----- apps/opencs/model/doc/documentmanager.cpp | 20 +++++++++++--------- apps/opencs/model/doc/documentmanager.hpp | 10 +++++++--- 5 files changed, 41 insertions(+), 24 deletions(-) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index ead4d2a982..a430597953 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -11,7 +11,7 @@ CS::Editor::Editor() -: mDocumentManager (mCfgMgr.getUserPath() / "projects"), mViewManager (mDocumentManager) +: mDocumentManager (mCfgMgr), mViewManager (mDocumentManager) { mIpcServerName = "org.openmw.OpenCS"; diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index 7f609f9f7e..5c29d9f617 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -4,6 +4,10 @@ #include +#ifndef Q_MOC_RUN +#include +#endif + void CSMDoc::Document::load (const std::vector::const_iterator& begin, const std::vector::const_iterator& end, bool lastAsModified) { @@ -2142,10 +2146,13 @@ void CSMDoc::Document::createBase() } } -CSMDoc::Document::Document (const std::vector& files, - const boost::filesystem::path& savePath, bool new_, - const boost::filesystem::path& projectPath) -: mSavePath (savePath), mContentFiles (files), mTools (mData), mSaving (*this, projectPath) +CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, + const std::vector& files, + const boost::filesystem::path& savePath, bool new_) +: mSavePath (savePath), mContentFiles (files), mTools (mData), + mProjectPath ((configuration.getUserPath() / "projects") / + (savePath.filename().string() + ".project")), + mSaving (*this, mProjectPath) { if (files.empty()) throw std::runtime_error ("Empty content file sequence"); @@ -2170,9 +2177,9 @@ CSMDoc::Document::Document (const std::vector& files, /// \todo un-outcomment the else, once loading an existing content file works properly again. // else { - if (boost::filesystem::exists (projectPath)) + if (boost::filesystem::exists (mProjectPath)) { - getData().loadFile (projectPath, false, true); + getData().loadFile (mProjectPath, false, true); } else { diff --git a/apps/opencs/model/doc/document.hpp b/apps/opencs/model/doc/document.hpp index 7843cfbfe9..d171dacaed 100644 --- a/apps/opencs/model/doc/document.hpp +++ b/apps/opencs/model/doc/document.hpp @@ -24,6 +24,11 @@ namespace ESM struct Global; } +namespace Files +{ + class ConfigurationManager; +} + namespace CSMDoc { class Document : public QObject @@ -36,6 +41,7 @@ namespace CSMDoc std::vector mContentFiles; CSMWorld::Data mData; CSMTools::Tools mTools; + boost::filesystem::path mProjectPath; Saving mSaving; // It is important that the undo stack is declared last, because on desctruction it fires a signal, that is connected to a slot, that is @@ -64,11 +70,9 @@ namespace CSMDoc public: - Document (const std::vector& files, - const boost::filesystem::path& savePath, bool new_, - const boost::filesystem::path& projectPath); - ///< \param projectPath Location of file that can be used to store additional data for - /// this project. + Document (const Files::ConfigurationManager& configuration, + const std::vector& files, + const boost::filesystem::path& savePath, bool new_); ~Document(); diff --git a/apps/opencs/model/doc/documentmanager.cpp b/apps/opencs/model/doc/documentmanager.cpp index 1978c0e536..1d6c88dccf 100644 --- a/apps/opencs/model/doc/documentmanager.cpp +++ b/apps/opencs/model/doc/documentmanager.cpp @@ -6,13 +6,19 @@ #include +#ifndef Q_MOC_RUN +#include +#endif + #include "document.hpp" -CSMDoc::DocumentManager::DocumentManager (const boost::filesystem::path& projectPath) -: mProjectPath (projectPath) +CSMDoc::DocumentManager::DocumentManager (const Files::ConfigurationManager& configuration) +: mConfiguration (configuration) { - if (!boost::filesystem::is_directory (mProjectPath)) - boost::filesystem::create_directories (mProjectPath); + boost::filesystem::path projectPath = configuration.getUserPath() / "projects"; + + if (!boost::filesystem::is_directory (projectPath)) + boost::filesystem::create_directories (projectPath); } CSMDoc::DocumentManager::~DocumentManager() @@ -24,11 +30,7 @@ CSMDoc::DocumentManager::~DocumentManager() CSMDoc::Document *CSMDoc::DocumentManager::addDocument (const std::vector& files, const boost::filesystem::path& savePath, bool new_) { - boost::filesystem::path projectFile (mProjectPath); - - projectFile /= savePath.filename().string() + ".project"; - - Document *document = new Document (files, savePath, new_, projectFile); + Document *document = new Document (mConfiguration, files, savePath, new_); mDocuments.push_back (document); diff --git a/apps/opencs/model/doc/documentmanager.hpp b/apps/opencs/model/doc/documentmanager.hpp index 622a135a58..28a21216a6 100644 --- a/apps/opencs/model/doc/documentmanager.hpp +++ b/apps/opencs/model/doc/documentmanager.hpp @@ -6,6 +6,11 @@ #include +namespace Files +{ + class ConfigurationManager; +} + namespace CSMDoc { class Document; @@ -13,15 +18,14 @@ namespace CSMDoc class DocumentManager { std::vector mDocuments; - boost::filesystem::path mProjectPath; + const Files::ConfigurationManager& mConfiguration; DocumentManager (const DocumentManager&); DocumentManager& operator= (const DocumentManager&); public: - DocumentManager (const boost::filesystem::path& projectPath); - ///< \param projectPath Directory where additional per-project data will be stored. + DocumentManager (const Files::ConfigurationManager& configuration); ~DocumentManager(); From 2537384c502c8cc74769bcd7d968542eff82ce2a Mon Sep 17 00:00:00 2001 From: gus Date: Sat, 28 Sep 2013 12:25:37 +0200 Subject: [PATCH 066/434] bug fix --- apps/openmw/mwmechanics/aicombat.cpp | 98 ++++++++++++++------------ apps/openmw/mwmechanics/aisequence.cpp | 51 +------------- 2 files changed, 54 insertions(+), 95 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 071aa50e39..4988dc9e31 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -22,55 +22,59 @@ namespace MWMechanics { const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();//MWBase::Environment::get().getWorld()->getPtr(mTargetId, false); - MWMechanics::DrawState_ state = MWWorld::Class::get(actor).getNpcStats(actor).getDrawState(); - if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing) - MWWorld::Class::get(actor).getNpcStats(actor).setDrawState(MWMechanics::DrawState_Weapon); - MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(true); - - ESM::Position pos = actor.getRefData().getPosition(); - const ESM::Pathgrid *pathgrid = - MWBase::Environment::get().getWorld()->getStore().get().search(*actor.getCell()->mCell); - - int cellX = actor.getCell()->mCell->mData.mX; - int cellY = actor.getCell()->mCell->mData.mY; - float xCell = 0; - float yCell = 0; - - if (actor.getCell()->mCell->isExterior()) + if(actor.getTypeName() == typeid(ESM::NPC).name()) { - xCell = actor.getCell()->mCell->mData.mX * ESM::Land::REAL_SIZE; - yCell = actor.getCell()->mCell->mData.mY * ESM::Land::REAL_SIZE; + + MWMechanics::DrawState_ state = MWWorld::Class::get(actor).getNpcStats(actor).getDrawState(); + if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing) + MWWorld::Class::get(actor).getNpcStats(actor).setDrawState(MWMechanics::DrawState_Weapon); + MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(true); + + ESM::Position pos = actor.getRefData().getPosition(); + const ESM::Pathgrid *pathgrid = + MWBase::Environment::get().getWorld()->getStore().get().search(*actor.getCell()->mCell); + + int cellX = actor.getCell()->mCell->mData.mX; + int cellY = actor.getCell()->mCell->mData.mY; + float xCell = 0; + float yCell = 0; + + if (actor.getCell()->mCell->isExterior()) + { + xCell = actor.getCell()->mCell->mData.mX * ESM::Land::REAL_SIZE; + yCell = actor.getCell()->mCell->mData.mY * ESM::Land::REAL_SIZE; + } + + ESM::Pathgrid::Point dest; + dest.mX = target.getRefData().getPosition().pos[0]; + dest.mY = target.getRefData().getPosition().pos[1]; + dest.mZ = target.getRefData().getPosition().pos[2]; + + ESM::Pathgrid::Point start; + start.mX = pos.pos[0]; + start.mY = pos.pos[1]; + start.mZ = pos.pos[2]; + + std::cout << start.mX << " " << dest.mX << "\n"; + + mPathFinder.buildPath(start, dest, pathgrid, xCell, yCell, true); + + mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2]) + + float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); + std::cout << zAngle; + MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false); + MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; + + float range = 100; + + if((dest.mX - start.mX)*(dest.mX - start.mX)+(dest.mY - start.mY)*(dest.mY - start.mY)+(dest.mZ - start.mZ)*(dest.mZ - start.mZ) + < range*range) + { + MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0; + //MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(!MWWorld::Class::get(actor).getCreatureStats(actor).getAttackingOrSpell()); + } } - - ESM::Pathgrid::Point dest; - dest.mX = target.getRefData().getPosition().pos[0]; - dest.mY = target.getRefData().getPosition().pos[1]; - dest.mZ = target.getRefData().getPosition().pos[2]; - - ESM::Pathgrid::Point start; - start.mX = pos.pos[0]; - start.mY = pos.pos[1]; - start.mZ = pos.pos[2]; - - std::cout << start.mX << " " << dest.mX << "\n"; - - mPathFinder.buildPath(start, dest, pathgrid, xCell, yCell, true); - - if(mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2])) - { - MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0; - } - - float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); - std::cout << zAngle; - MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false); - MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; - - if(dest.mX - start.mX < 100) - { - //MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(false); - } - return false; } diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index d02f03e18d..f3aa877e24 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -68,57 +68,12 @@ void MWMechanics::AiSequence::execute (const MWWorld::Ptr& actor) { if(mCombat) { - //mCombatPackage->execute(actor); + mCombatPackage->execute(actor); } else { - //mCombat = true; - //mCombatPackage = new AiCombat("player"); - - /*if(actor != MWBase::Environment::get().getWorld()->getPlayer().getPlayer()) - { - MWMechanics::DrawState_ state = MWWorld::Class::get(actor).getNpcStats(actor).getDrawState(); - if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing) - MWWorld::Class::get(actor).getNpcStats(actor).setDrawState(MWMechanics::DrawState_Weapon); - MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(true); - - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); - ESM::Position pos = actor.getRefData().getPosition(); - const ESM::Pathgrid *pathgrid = - MWBase::Environment::get().getWorld()->getStore().get().search(*actor.getCell()->mCell); - - int cellX = actor.getCell()->mCell->mData.mX; - int cellY = actor.getCell()->mCell->mData.mY; - float xCell = 0; - float yCell = 0; - - if (actor.getCell()->mCell->isExterior()) - { - xCell = actor.getCell()->mCell->mData.mX * ESM::Land::REAL_SIZE; - yCell = actor.getCell()->mCell->mData.mY * ESM::Land::REAL_SIZE; - } - - ESM::Pathgrid::Point dest; - dest.mX = player.getRefData().getPosition().pos[0]; - dest.mY = player.getRefData().getPosition().pos[1]; - dest.mZ = player.getRefData().getPosition().pos[2]; - - ESM::Pathgrid::Point start; - start.mX = pos.pos[0]; - start.mY = pos.pos[1]; - start.mZ = pos.pos[2]; - - PathFinder mPathFinder; - mPathFinder.buildPath(start, dest, pathgrid, xCell, yCell, true); - float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); - MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false); - MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; - - if(dest.mX - start.mX < 100) - { - MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(false); - } - }*/ + mCombat = true; + mCombatPackage = new AiCombat("player"); if (!mPackages.empty()) { if (mPackages.front()->execute (actor)) From 83a375b55d6f6a0b93f476c92fedf385dadd216b Mon Sep 17 00:00:00 2001 From: gus Date: Sat, 28 Sep 2013 21:17:06 +0200 Subject: [PATCH 067/434] AI now continue to hit you. TODO: change the way time is handled --- apps/openmw/mwmechanics/aicombat.cpp | 17 ++++++++++++++--- apps/openmw/mwmechanics/aicombat.hpp | 1 + 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 4988dc9e31..bf5968e8c7 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -14,7 +14,7 @@ namespace MWMechanics { AiCombat::AiCombat(const std::string &targetId) - :mTargetId(targetId) + :mTargetId(targetId),mStartingSecond(0) { } @@ -28,7 +28,7 @@ namespace MWMechanics MWMechanics::DrawState_ state = MWWorld::Class::get(actor).getNpcStats(actor).getDrawState(); if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing) MWWorld::Class::get(actor).getNpcStats(actor).setDrawState(MWMechanics::DrawState_Weapon); - MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(true); + //MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(true); ESM::Position pos = actor.getRefData().getPosition(); const ESM::Pathgrid *pathgrid = @@ -59,7 +59,7 @@ namespace MWMechanics mPathFinder.buildPath(start, dest, pathgrid, xCell, yCell, true); - mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2]) + mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2]); float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); std::cout << zAngle; @@ -71,6 +71,17 @@ namespace MWMechanics if((dest.mX - start.mX)*(dest.mX - start.mX)+(dest.mY - start.mY)*(dest.mY - start.mY)+(dest.mZ - start.mZ)*(dest.mZ - start.mZ) < range*range) { + MWWorld::TimeStamp time = MWBase::Environment::get().getWorld()->getTimeStamp(); + if(mStartingSecond == 0) + { + MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(false); + mStartingSecond = ((time.getHour() - int(time.getHour())) * 100); + } + else if( ((time.getHour() - int(time.getHour())) * 100) - mStartingSecond > 1) + { + MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(true); + mStartingSecond = 0; + } MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0; //MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(!MWWorld::Class::get(actor).getCreatureStats(actor).getAttackingOrSpell()); } diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 3b2ae6fc0b..bde66a46bc 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -33,6 +33,7 @@ namespace MWMechanics std::string mTargetId; PathFinder mPathFinder; + unsigned int mStartingSecond; }; } From 9c2145eda126cb05abe2f84213fce6fa242b92e5 Mon Sep 17 00:00:00 2001 From: Lukasz Gromanowski Date: Sun, 29 Sep 2013 09:11:57 +0200 Subject: [PATCH 068/434] Issue #913: Merge --master and --plugin switches Merged master/plugin switches into content in openmw and mwiniimporter. Extension in content files is now required. Signed-off-by: Lukasz Gromanowski --- apps/mwiniimporter/importer.cpp | 25 ++----- apps/openmw/CMakeLists.txt | 1 + apps/openmw/engine.cpp | 40 +++-------- apps/openmw/engine.hpp | 15 ++-- apps/openmw/main.cpp | 36 +++------- apps/openmw/mwworld/contentloader.hpp | 35 +++++++++ apps/openmw/mwworld/esmloader.cpp | 31 ++++++++ apps/openmw/mwworld/esmloader.hpp | 34 +++++++++ apps/openmw/mwworld/omwloader.cpp | 17 +++++ apps/openmw/mwworld/omwloader.hpp | 21 ++++++ apps/openmw/mwworld/worldimp.cpp | 100 +++++++++++++++++--------- apps/openmw/mwworld/worldimp.hpp | 15 +++- 12 files changed, 254 insertions(+), 116 deletions(-) create mode 100644 apps/openmw/mwworld/contentloader.hpp create mode 100644 apps/openmw/mwworld/esmloader.cpp create mode 100644 apps/openmw/mwworld/esmloader.hpp create mode 100644 apps/openmw/mwworld/omwloader.cpp create mode 100644 apps/openmw/mwworld/omwloader.hpp diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index 8732b3eab3..b8b7e4c9da 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -813,8 +813,7 @@ void MwIniImporter::importArchives(multistrmap &cfg, const multistrmap &ini) con } void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini) const { - std::vector esmFiles; - std::vector espFiles; + std::vector contentFiles; std::string baseGameFile("Game Files:GameFile"); std::string gameFile(""); @@ -832,29 +831,19 @@ void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini) co std::string filetype(entry->substr(entry->length()-3)); Misc::StringUtils::toLower(filetype); - if(filetype.compare("esm") == 0) { - esmFiles.push_back(*entry); - } - else if(filetype.compare("esp") == 0) { - espFiles.push_back(*entry); + if(filetype.compare("esm") == 0 || filetype.compare("esp") == 0) { + contentFiles.push_back(*entry); } } gameFile = ""; } - cfg.erase("master"); - cfg.insert( std::make_pair > ("master", std::vector() ) ); + cfg.erase("content"); + cfg.insert( std::make_pair("content", std::vector() ) ); - for(std::vector::const_iterator it=esmFiles.begin(); it!=esmFiles.end(); ++it) { - cfg["master"].push_back(*it); - } - - cfg.erase("plugin"); - cfg.insert( std::make_pair > ("plugin", std::vector() ) ); - - for(std::vector::const_iterator it=espFiles.begin(); it!=espFiles.end(); ++it) { - cfg["plugin"].push_back(*it); + for(std::vector::const_iterator it=contentFiles.begin(); it!=contentFiles.end(); ++it) { + cfg["content"].push_back(*it); } } diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index b367e2a1e7..807b1b5ff1 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -58,6 +58,7 @@ add_openmw_dir (mwworld cells localscripts customdata weather inventorystore ptr actionopen actionread actionequip timestamp actionalchemy cellstore actionapply actioneat esmstore store recordcmp fallback actionrepair actionsoulgem livecellref actiondoor + contentloader esmloader omwloader ) add_openmw_dir (mwclass diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index a2eccbaf9a..d29301ed35 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -261,34 +261,14 @@ void OMW::Engine::setCell (const std::string& cellName) mCellName = cellName; } -// Set master file (esm) -// - If the given name does not have an extension, ".esm" is added automatically - -void OMW::Engine::addMaster (const std::string& master) +void OMW::Engine::addContentFile(const std::string& file) { - mMaster.push_back(master); - std::string &str = mMaster.back(); + if (file.find_last_of(".") == std::string::npos) + { + throw std::runtime_error("Missing extension in content file!"); + } - // Append .esm if not already there - std::string::size_type sep = str.find_last_of ("."); - if (sep == std::string::npos) - { - str += ".esm"; - } -} - -// Add plugin file (esp) -void OMW::Engine::addPlugin (const std::string& plugin) -{ - mPlugins.push_back(plugin); - std::string &str = mPlugins.back(); - - // Append .esp if not already there - std::string::size_type sep = str.find_last_of ("."); - if (sep == std::string::npos) - { - str += ".esp"; - } + mContentFiles.push_back(file); } void OMW::Engine::setScriptsVerbosity(bool scriptsVerbosity) @@ -403,7 +383,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) mEnvironment.getWindowManager()->setNewGame(true); // Create the world - mEnvironment.setWorld( new MWWorld::World (*mOgre, mFileCollections, mMaster, mPlugins, + mEnvironment.setWorld( new MWWorld::World (*mOgre, mFileCollections, mContentFiles, mResDir, mCfgMgr.getCachePath(), mEncoder, mFallbackMap, mActivationDistanceOverride)); MWBase::Environment::get().getWorld()->setupPlayer(); @@ -414,8 +394,8 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) //Load translation data mTranslationDataStorage.setEncoder(mEncoder); - for (size_t i = 0; i < mMaster.size(); i++) - mTranslationDataStorage.loadTranslationData(mFileCollections, mMaster[i]); + for (size_t i = 0; i < mContentFiles.size(); i++) + mTranslationDataStorage.loadTranslationData(mFileCollections, mContentFiles[i]); Compiler::registerExtensions (mExtensions); @@ -480,7 +460,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) void OMW::Engine::go() { assert (!mCellName.empty()); - assert (!mMaster.empty()); + assert (!mContentFiles.empty()); assert (!mOgre); Settings::Manager settings; diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index 665b0094c1..553d290687 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -68,8 +68,7 @@ namespace OMW boost::filesystem::path mResDir; OEngine::Render::OgreRenderer *mOgre; std::string mCellName; - std::vector mMaster; - std::vector mPlugins; + std::vector mContentFiles; int mFpsLevel; bool mVerboseScripts; bool mNewGame; @@ -135,13 +134,11 @@ namespace OMW /// Set start cell name (only interiors for now) void setCell(const std::string& cellName); - /// Set master file (esm) - /// - If the given name does not have an extension, ".esm" is added automatically - void addMaster(const std::string& master); - - /// Same as "addMaster", but for plugin files (esp) - /// - If the given name does not have an extension, ".esp" is added automatically - void addPlugin(const std::string& plugin); + /** + * @brief addContentFile - Adds content file (ie. esm/esp, or omwgame/omwaddon) to the content files container. + * @param file - filename (extension is required) + */ + void addContentFile(const std::string& file); /// Enable fps counter void showFPS(int level); diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 27afd734ae..33f740b311 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -110,11 +110,8 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat ("start", bpo::value()->default_value("Beshara"), "set initial cell") - ("master", bpo::value()->default_value(StringsVector(), "") - ->multitoken(), "master file(s)") - - ("plugin", bpo::value()->default_value(StringsVector(), "") - ->multitoken(), "plugin file(s)") + ("content", bpo::value()->default_value(StringsVector(), "") + ->multitoken(), "content file(s): esm/esp, or omwgame/omwaddon") ("anim-verbose", bpo::value()->implicit_value(true) ->default_value(false), "output animation indices files") @@ -152,8 +149,6 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat ("activate-dist", bpo::value ()->default_value (-1), "activation distance override"); - ; - bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv) .options(desc).allow_unregistered().run(); @@ -211,29 +206,18 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat engine.setResourceDir(variables["resources"].as()); - // master and plugin - StringsVector master = variables["master"].as(); - if (master.empty()) + StringsVector content = variables["content"].as(); + if (content.empty()) { - std::cout << "No master file given. Aborting...\n"; - return false; + std::cout << "No content file given (esm/esp, nor omwgame/omwaddon). Aborting..." << std::endl; + return false; } - StringsVector plugin = variables["plugin"].as(); - // Removed check for 255 files, which would be the hard-coded limit in Morrowind. - // I'll keep the following variable in, maybe we can use it for something different. - // Say, a feedback like "loading file x/cnt". - // Commenting this out for now to silence compiler warning. - //int cnt = master.size() + plugin.size(); - - // Prepare loading master/plugin files (i.e. send filenames to engine) - for (std::vector::size_type i = 0; i < master.size(); i++) + StringsVector::const_iterator it(content.begin()); + StringsVector::const_iterator end(content.end()); + for (; it != end; ++it) { - engine.addMaster(master[i]); - } - for (std::vector::size_type i = 0; i < plugin.size(); i++) - { - engine.addPlugin(plugin[i]); + engine.addContentFile(*it); } // startup-settings diff --git a/apps/openmw/mwworld/contentloader.hpp b/apps/openmw/mwworld/contentloader.hpp new file mode 100644 index 0000000000..c57935c907 --- /dev/null +++ b/apps/openmw/mwworld/contentloader.hpp @@ -0,0 +1,35 @@ +#ifndef CONTENTLOADER_HPP +#define CONTENTLOADER_HPP + +#include +#include + +#include "components/loadinglistener/loadinglistener.hpp" + +namespace MWWorld +{ + +struct ContentLoader +{ + ContentLoader(Loading::Listener& listener) + : mListener(listener) + { + } + + virtual ~ContentLoader() + { + } + + virtual void load(const boost::filesystem::path& filepath, int& index) + { + std::cout << "Loading content file " << filepath.string() << std::endl; + mListener.setLabel(filepath.string()); + } + + protected: + Loading::Listener& mListener; +}; + +} /* namespace MWWorld */ + +#endif /* CONTENTLOADER_HPP */ diff --git a/apps/openmw/mwworld/esmloader.cpp b/apps/openmw/mwworld/esmloader.cpp new file mode 100644 index 0000000000..1b8880d375 --- /dev/null +++ b/apps/openmw/mwworld/esmloader.cpp @@ -0,0 +1,31 @@ +#include "esmloader.hpp" +#include "esmstore.hpp" + +#include "components/to_utf8/to_utf8.hpp" + +namespace MWWorld +{ + +EsmLoader::EsmLoader(MWWorld::ESMStore& store, std::vector& readers, + ToUTF8::Utf8Encoder* encoder, Loading::Listener& listener) + : ContentLoader(listener) + , mStore(store) + , mEsm(readers) + , mEncoder(encoder) +{ +} + +void EsmLoader::load(const boost::filesystem::path& filepath, int& index) +{ + ContentLoader::load(filepath.filename(), index); + + ESM::ESMReader lEsm; + lEsm.setEncoder(mEncoder); + lEsm.setIndex(index); + lEsm.setGlobalReaderList(&mEsm); + lEsm.open(filepath.string()); + mEsm[index] = lEsm; + mStore.load(mEsm[index], &mListener); +} + +} /* namespace MWWorld */ diff --git a/apps/openmw/mwworld/esmloader.hpp b/apps/openmw/mwworld/esmloader.hpp new file mode 100644 index 0000000000..d799c3f152 --- /dev/null +++ b/apps/openmw/mwworld/esmloader.hpp @@ -0,0 +1,34 @@ +#ifndef ESMLOADER_HPP +#define ESMLOADER_HPP + +#include + +#include "contentloader.hpp" +#include "components/esm/esmreader.hpp" + +namespace ToUTF8 +{ + class Utf8Encoder; +} + +namespace MWWorld +{ + +class ESMStore; + +struct EsmLoader : public ContentLoader +{ + EsmLoader(MWWorld::ESMStore& store, std::vector& readers, + ToUTF8::Utf8Encoder* encoder, Loading::Listener& listener); + + void load(const boost::filesystem::path& filepath, int& index); + + private: + std::vector& mEsm; + MWWorld::ESMStore& mStore; + ToUTF8::Utf8Encoder* mEncoder; +}; + +} /* namespace MWWorld */ + +#endif // ESMLOADER_HPP diff --git a/apps/openmw/mwworld/omwloader.cpp b/apps/openmw/mwworld/omwloader.cpp new file mode 100644 index 0000000000..8562a4fe04 --- /dev/null +++ b/apps/openmw/mwworld/omwloader.cpp @@ -0,0 +1,17 @@ +#include "omwloader.hpp" + +namespace MWWorld +{ + +OmwLoader::OmwLoader(Loading::Listener& listener) + : ContentLoader(listener) +{ +} + +void OmwLoader::load(const boost::filesystem::path& filepath, int& index) +{ + ContentLoader::load(filepath.filename(), index); +} + +} /* namespace MWWorld */ + diff --git a/apps/openmw/mwworld/omwloader.hpp b/apps/openmw/mwworld/omwloader.hpp new file mode 100644 index 0000000000..cb9faa4303 --- /dev/null +++ b/apps/openmw/mwworld/omwloader.hpp @@ -0,0 +1,21 @@ +#ifndef OMWLOADER_HPP +#define OMWLOADER_HPP + +#include "contentloader.hpp" + +namespace MWWorld +{ + +/** + * @brief Placeholder for real OpenMW content loader + */ +struct OmwLoader : public ContentLoader +{ + OmwLoader(Loading::Listener& listener); + + void load(const boost::filesystem::path& filepath, int& index); +}; + +} /* namespace MWWorld */ + +#endif /* OMWLOADER_HPP */ diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index f3d4c81b76..b7b23e5c15 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1,4 +1,11 @@ #include "worldimp.hpp" +#ifdef _WIN32 +#include +#elif defined HAVE_UNORDERED_MAP +#include +#else +#include +#endif #include @@ -31,6 +38,10 @@ #include "containerstore.hpp" #include "inventorystore.hpp" +#include "contentloader.hpp" +#include "esmloader.hpp" +#include "omwloader.hpp" + using namespace Ogre; namespace @@ -80,6 +91,38 @@ namespace namespace MWWorld { + struct GameContentLoader : public ContentLoader + { + GameContentLoader(Loading::Listener& listener) + : ContentLoader(listener) + { + } + + bool addLoader(const std::string& extension, ContentLoader* loader) + { + return mLoaders.insert(std::make_pair(extension, loader)).second; + } + + void load(const boost::filesystem::path& filepath, int& index) + { + LoadersContainer::iterator it(mLoaders.find(filepath.extension().string())); + if (it != mLoaders.end()) + { + it->second->load(filepath, index); + } + else + { + std::string msg("Cannot load file: "); + msg += filepath.string(); + throw std::runtime_error(msg.c_str()); + } + } + + private: + typedef std::tr1::unordered_map LoadersContainer; + LoadersContainer mLoaders; + }; + Ptr World::getPtrViaHandle (const std::string& handle, Ptr::CellStore& cell) { if (MWWorld::LiveCellRef *ref = @@ -163,7 +206,7 @@ namespace MWWorld World::World (OEngine::Render::OgreRenderer& renderer, const Files::Collections& fileCollections, - const std::vector& master, const std::vector& plugins, + const std::vector& contentFiles, const boost::filesystem::path& resDir, const boost::filesystem::path& cacheDir, ToUTF8::Utf8Encoder* encoder, const std::map& fallbackMap, int mActivationDistanceOverride) : mPlayer (0), mLocalScripts (mStore), mGlobalVariables (0), @@ -181,44 +224,22 @@ namespace MWWorld mWeatherManager = new MWWorld::WeatherManager(mRendering,&mFallback); - int idx = 0; // NOTE: We might need to reserve one more for the running game / save. - mEsm.resize(master.size() + plugins.size()); + mEsm.resize(contentFiles.size()); Loading::Listener* listener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); listener->loadingOn(); - for (std::vector::size_type i = 0; i < master.size(); i++, idx++) - { - boost::filesystem::path masterPath (fileCollections.getCollection (".esm").getPath (master[i])); - std::cout << "Loading ESM " << masterPath.string() << "\n"; - listener->setLabel(masterPath.filename().string()); + GameContentLoader gameContentLoader(*listener); + EsmLoader esmLoader(mStore, mEsm, encoder, *listener); + OmwLoader omwLoader(*listener); - // This parses the ESM file - ESM::ESMReader lEsm; - lEsm.setEncoder(encoder); - lEsm.setIndex(idx); - lEsm.setGlobalReaderList(&mEsm); - lEsm.open (masterPath.string()); - mEsm[idx] = lEsm; - mStore.load (mEsm[idx], listener); - } + gameContentLoader.addLoader(".esm", &esmLoader); + gameContentLoader.addLoader(".esp", &esmLoader); + gameContentLoader.addLoader(".omwgame", &omwLoader); + gameContentLoader.addLoader(".omwaddon", &omwLoader); - for (std::vector::size_type i = 0; i < plugins.size(); i++, idx++) - { - boost::filesystem::path pluginPath (fileCollections.getCollection (".esp").getPath (plugins[i])); + loadContentFiles(fileCollections, contentFiles, gameContentLoader); - std::cout << "Loading ESP " << pluginPath.string() << "\n"; - listener->setLabel(pluginPath.filename().string()); - - // This parses the ESP file - ESM::ESMReader lEsm; - lEsm.setEncoder(encoder); - lEsm.setIndex(idx); - lEsm.setGlobalReaderList(&mEsm); - lEsm.open (pluginPath.string()); - mEsm[idx] = lEsm; - mStore.load (mEsm[idx], listener); - } listener->loadingOff(); // insert records that may not be present in all versions of MW @@ -1960,4 +1981,19 @@ namespace MWWorld return mGodMode; } + void World::loadContentFiles(const Files::Collections& fileCollections, + const std::vector& content, ContentLoader& contentLoader) + { + std::vector::const_iterator it(content.begin()); + std::vector::const_iterator end(content.end()); + for (int idx = 0; it != end; ++it, ++idx) + { + boost::filesystem::path filename(*it); + const Files::MultiDirCollection& col = fileCollections.getCollection(filename.extension().string()); + if (col.doesExist(*it)) + { + contentLoader.load(col.getPath(*it), idx); + } + } + } } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 53b01f1abf..d39189282e 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -14,6 +14,8 @@ #include "../mwbase/world.hpp" +#include "contentloader.hpp" + namespace Ogre { class Vector3; @@ -41,6 +43,8 @@ namespace MWRender class Animation; } +struct ContentLoader; + namespace MWWorld { class WeatherManager; @@ -113,6 +117,15 @@ namespace MWWorld void ensureNeededRecords(); + /** + * @brief loadContentFiles - Loads content files (esm,esp,omwgame,omwaddon) + * @param fileCollections- Container which holds content file names and their paths + * @param content - Container which holds content file names + * @param contentLoader - + */ + void loadContentFiles(const Files::Collections& fileCollections, + const std::vector& content, ContentLoader& contentLoader); + int mPlayIntro; bool mTeleportEnabled; @@ -121,7 +134,7 @@ namespace MWWorld World (OEngine::Render::OgreRenderer& renderer, const Files::Collections& fileCollections, - const std::vector& master, const std::vector& plugins, + const std::vector& contentFiles, const boost::filesystem::path& resDir, const boost::filesystem::path& cacheDir, ToUTF8::Utf8Encoder* encoder, const std::map& fallbackMap, int mActivationDistanceOverride); From ef617d408b7baa09e704db40b8b157608ac9da98 Mon Sep 17 00:00:00 2001 From: Lukasz Gromanowski Date: Sun, 29 Sep 2013 09:14:40 +0200 Subject: [PATCH 069/434] Issue #913: Merge --master and --plugin switches Merged master/plugin switches in launcher. Signed-off-by: Lukasz Gromanowski --- apps/launcher/datafilespage.cpp | 9 +++++---- apps/launcher/settings/gamesettings.cpp | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 44392794bd..43f09d1687 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -206,19 +206,20 @@ void DataFilesPage::saveSettings() mLauncherSettings.remove(QString("Profiles/") + profile + QString("/plugin")); mGameSettings.remove(QString("master")); - mGameSettings.remove(QString("plugin")); + mGameSettings.remove(QString("plugins")); + mGameSettings.remove(QString("content")); - ContentSelectorModel::ContentFileList items = mContentModel->checkedItems(); + ContentSelectorModel::ContentFileList items = mContentModel->checkedItems(); foreach(const ContentSelectorModel::EsmFile *item, items) { if (item->gameFiles().size() == 0) { mLauncherSettings.setMultiValue(QString("Profiles/") + profile + QString("/master"), item->fileName()); - mGameSettings.setMultiValue(QString("master"), item->fileName()); + mGameSettings.setMultiValue(QString("content"), item->fileName()); } else { mLauncherSettings.setMultiValue(QString("Profiles/") + profile + QString("/plugin"), item->fileName()); - mGameSettings.setMultiValue(QString("plugin"), item->fileName()); + mGameSettings.setMultiValue(QString("content"), item->fileName()); } } diff --git a/apps/launcher/settings/gamesettings.cpp b/apps/launcher/settings/gamesettings.cpp index 205879bc37..7b2356cd08 100644 --- a/apps/launcher/settings/gamesettings.cpp +++ b/apps/launcher/settings/gamesettings.cpp @@ -163,12 +163,12 @@ bool GameSettings::writeFile(QTextStream &stream) QStringList masters = mSettings.values(QString("master")); for (int i = masters.count(); i--;) { - stream << "master=" << masters.at(i) << "\n"; + stream << "content=" << masters.at(i) << "\n"; } QStringList plugins = mSettings.values(QString("plugin")); for (int i = plugins.count(); i--;) { - stream << "plugin=" << plugins.at(i) << "\n"; + stream << "content=" << plugins.at(i) << "\n"; } return true; From 24b167b7552ce8bf6e62933a535752a899c77473 Mon Sep 17 00:00:00 2001 From: graffy76 Date: Sun, 29 Sep 2013 12:19:07 -0500 Subject: [PATCH 070/434] Implemented ContentSelector as a singleton "charm" modifier for FileDialog... --- apps/launcher/datafilespage.cpp | 11 +- apps/opencs/editor.cpp | 13 +- apps/opencs/view/doc/filedialog.cpp | 103 +++------ apps/opencs/view/doc/filedialog.hpp | 38 ++-- .../contentselector/view/contentselector.cpp | 213 +++++++++++++++--- .../contentselector/view/contentselector.hpp | 51 ++++- 6 files changed, 285 insertions(+), 144 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 44392794bd..828b2f2ba2 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -29,8 +29,7 @@ DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gam , mLauncherSettings(launcherSettings) { setupUi(this); - // mContentSelector.setParent(parent); - +/* // QMetaObject::connectSlotsByName(this); projectGroupBox->hide(); @@ -52,7 +51,7 @@ DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gam setupDataFiles(); - updateViews(); + updateViews();*/ } void DataFilesPage::buildContentModel() @@ -112,10 +111,10 @@ void DataFilesPage::updateViews() void ContentSelectorView::ContentSelector::addFiles(const QString &path) { - mContentModel->addFiles(path); + // mContentModel->addFiles(path); //mContentModel->sort(3); // Sort by date accessed - gameFileView->setCurrentIndex(-1); - mContentModel->uncheckAll(); + // ui.gameFileView->setCurrentIndex(-1); + // mContentModel->uncheckAll(); } void DataFilesPage::createActions() diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index ba1dfb57ec..fc9168e2ee 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -123,37 +123,34 @@ void CS::Editor::loadDocument() void CS::Editor::openFiles() { std::vector files; - QStringList paths = mFileDialog.checkedItemsPaths(); - foreach (const QString &path, paths) { + foreach (const QString &path, mFileDialog.selectedFilepaths()) { files.push_back(path.toStdString()); } /// \todo Get the save path from the file dialogue - CSMDoc::Document *document = mDocumentManager.addDocument (files, *files.rbegin(), false); mViewManager.addView (document); - mFileDialog.hide(); + mFileDialog.close(); } void CS::Editor::createNewFile() { std::vector files; - QStringList paths = mFileDialog.checkedItemsPaths(); - foreach (const QString &path, paths) { + foreach (const QString &path, mFileDialog.selectedFilepaths()) { files.push_back(path.toStdString()); } - files.push_back(mFileDialog.fileName().toStdString()); + files.push_back(mFileDialog.filename().toStdString()); /// \todo Get the save path from the file dialogue. CSMDoc::Document *document = mDocumentManager.addDocument (files, *files.rbegin(), true); mViewManager.addView (document); - mFileDialog.hide(); + mFileDialog.close(); } void CS::Editor::createNewGame (const boost::filesystem::path& file) diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index 68aab27d5f..5a97a7a26d 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -10,107 +10,76 @@ #include #include #include +#include #include #include - -#include "filewidget.hpp" -#include "adjusterwidget.hpp" +#include "components/contentselector/view/contentselector.hpp" #include CSVDoc::FileDialog::FileDialog(QWidget *parent) : - ContentSelector(parent), - mFileWidget (new FileWidget (this)), - mAdjusterWidget (new AdjusterWidget (this)), - mEnable_1(false), - mEnable_2(false) -{ - // Hide the profile elements - profileGroupBox->hide(); - addonView->showColumn(2); + QDialog(parent), + mOpenFileFlags (ContentSelectorView::Flag_Content | ContentSelectorView::Flag_LoadAddon), + mNewFileFlags (ContentSelectorView::Flag_Content | ContentSelectorView::Flag_NewAddon) +{ resize(400, 400); - - mFileWidget->setType(true); - mFileWidget->extensionLabelIsVisible(false); - - connect(projectCreateButton, SIGNAL(clicked()), this, SIGNAL(createNewFile())); - - connect(projectButtonBox, SIGNAL(accepted()), this, SIGNAL(openFiles())); - connect(projectButtonBox, SIGNAL(rejected()), this, SLOT(reject())); - - connect (mFileWidget, SIGNAL (nameChanged (const QString&, bool)), - mAdjusterWidget, SLOT (setName (const QString&, bool))); - - connect (mAdjusterWidget, SIGNAL (stateChanged (bool)), this, SLOT (slotAdjusterChanged(bool))); - connect (this, SIGNAL (signalGameFileChanged(int)), this, SLOT (slotGameFileSelected(int))); - connect (this, SIGNAL (signalUpdateCreateButton(bool, int)), this, SLOT (slotEnableCreateButton(bool, int))); } -void CSVDoc::FileDialog::updateOpenButton(const QStringList &items) +void CSVDoc::FileDialog::addFiles(const QString &path) { - QPushButton *openButton = projectButtonBox->button(QDialogButtonBox::Open); - - if (!openButton) - return; - - openButton->setEnabled(!items.isEmpty()); + ContentSelectorView::ContentSelector::addFiles(path); } -void CSVDoc::FileDialog::slotEnableCreateButton(bool enable, int widgetNumber) +QString CSVDoc::FileDialog::filename() { - - if (widgetNumber == 1) - mEnable_1 = enable; - - if (widgetNumber == 2) - mEnable_2 = enable; - - projectCreateButton->setEnabled(mEnable_1 && mEnable_2); + return ContentSelectorView::ContentSelector::instance().filename(); } -QString CSVDoc::FileDialog::fileName() +QStringList CSVDoc::FileDialog::selectedFilepaths() { - return mFileWidget->getName(); + return ContentSelectorView::ContentSelector::instance().selectedFiles(); +} + +void CSVDoc::FileDialog::showDialog() +{ + show(); + raise(); + activateWindow(); } void CSVDoc::FileDialog::openFile() { setWindowTitle(tr("Open")); - mFileWidget->hide(); - adjusterWidgetFrame->hide(); - projectCreateButton->hide(); - projectGroupBox->setTitle(tr("")); - projectButtonBox->button(QDialogButtonBox::Open)->setEnabled(false); + ContentSelectorView::ContentSelector::configure(this, mOpenFileFlags); - show(); - raise(); - activateWindow(); + connect (&ContentSelectorView::ContentSelector::instance(), + SIGNAL (accepted()), this, SIGNAL (openFiles())); + + connect (&ContentSelectorView::ContentSelector::instance(), + SIGNAL (rejected()), this, SLOT (slotRejected())); + + showDialog(); } void CSVDoc::FileDialog::newFile() { setWindowTitle(tr("New")); - fileWidgetFrame->layout()->addWidget(mFileWidget); - adjusterWidgetFrame->layout()->addWidget(mAdjusterWidget); + ContentSelectorView::ContentSelector::configure(this, mNewFileFlags); - projectButtonBox->setStandardButtons(QDialogButtonBox::Cancel); - projectButtonBox->addButton(projectCreateButton, QDialogButtonBox::ActionRole); + connect (&ContentSelectorView::ContentSelector::instance(), + SIGNAL (accepted()), this, SIGNAL (createNewFile())); - show(); - raise(); - activateWindow(); + connect (&ContentSelectorView::ContentSelector::instance(), + SIGNAL (rejected()), this, SLOT (slotRejected())); + + showDialog(); } -void CSVDoc::FileDialog::slotAdjusterChanged(bool value) +void CSVDoc::FileDialog::slotRejected() { - emit signalUpdateCreateButton(mAdjusterWidget->isValid(), 2); -} - -void CSVDoc::FileDialog::slotGameFileSelected(int value) -{ - emit signalUpdateCreateButton(value > -1, 1); + close(); } diff --git a/apps/opencs/view/doc/filedialog.hpp b/apps/opencs/view/doc/filedialog.hpp index 7782dd94ea..88d408b5c6 100644 --- a/apps/opencs/view/doc/filedialog.hpp +++ b/apps/opencs/view/doc/filedialog.hpp @@ -3,9 +3,7 @@ #include #include - -#include "components/contentselector/view/contentselector.hpp" -#include "ui_datafilespage.h" +#include "../../../../components/contentselector/view/contentselector.hpp" class QDialogButtonBox; class QSortFilterProxyModel; @@ -19,6 +17,8 @@ class QLabel; class DataFilesModel; class PluginsProxyModel; + + namespace ContentSelectorView { class LineEdit; @@ -26,42 +26,38 @@ namespace ContentSelectorView namespace CSVDoc { - class FileWidget; - class AdjusterWidget; - - class FileDialog : public ContentSelectorView::ContentSelector + class FileDialog : public QDialog { Q_OBJECT - FileWidget *mFileWidget; - AdjusterWidget *mAdjusterWidget; - - bool mEnable_1; - bool mEnable_2; + unsigned char mOpenFileFlags; + unsigned char mNewFileFlags; public: explicit FileDialog(QWidget *parent = 0); void openFile(); void newFile(); + void addFiles (const QString &path); - QString fileName(); + QString filename(); + QStringList selectedFilepaths(); + + private: + + void showDialog(); signals: + void openFiles(); void createNewFile(); - void signalUpdateCreateButton (bool, int); - void signalUpdateCreateButtonFlags(int); - public slots: + void slotRejected(); + private slots: - //void updateViews(); - void updateOpenButton(const QStringList &items); - void slotEnableCreateButton(bool enable, int widgetNumber); - void slotAdjusterChanged(bool value); - void slotGameFileSelected(int value); + }; } #endif // FILEDIALOG_HPP diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index e6ed0ec567..d9caea3bed 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -8,83 +8,205 @@ #include #include #include +#include +#include -ContentSelectorView::ContentSelector::ContentSelector(QWidget *parent) : - QDialog(parent) +#include "../../../apps/opencs/view/doc/filewidget.hpp" +#include "../../../apps/opencs/view/doc/adjusterwidget.hpp" + +ContentSelectorView::ContentSelector *ContentSelectorView::ContentSelector::mInstance = 0; +QStringList ContentSelectorView::ContentSelector::mFilePaths; + +void ContentSelectorView::ContentSelector::configure(QWidget *subject, unsigned char flags) { - setupUi(this); + assert(!mInstance); + mInstance = new ContentSelector (subject, flags); +} + +ContentSelectorView::ContentSelector& ContentSelectorView::ContentSelector::instance() +{ + assert(mInstance); + return *mInstance; +} + +ContentSelectorView::ContentSelector::ContentSelector(QWidget *parent, unsigned char flags) : + QWidget(parent), mFlags (flags), + mAdjusterWidget (0), mFileWidget (0) +{ + ui.setupUi (this); + + parent->setLayout(new QGridLayout()); + parent->layout()->addWidget(this); buildContentModel(); buildGameFileView(); buildAddonView(); buildProfilesView(); + buildNewAddonView(); + buildLoadAddonView(); - updateViews(); + /* + //mContentModel->sort(3); // Sort by date accessed +*/ +} + +bool ContentSelectorView::ContentSelector::isFlagged(SelectorFlags flag) const +{ + return (mFlags & flag); } void ContentSelectorView::ContentSelector::buildContentModel() { + if (!isFlagged (Flag_Content)) + return; + mContentModel = new ContentSelectorModel::ContentModel(); - connect(mContentModel, SIGNAL(layoutChanged()), this, SLOT(updateViews())); + + if (mFilePaths.size()>0) + { + foreach (const QString &path, mFilePaths) + mContentModel->addFiles(path); + + mFilePaths.clear(); + } + + ui.gameFileView->setCurrentIndex(-1); + mContentModel->uncheckAll(); } void ContentSelectorView::ContentSelector::buildGameFileView() { + if (!isFlagged (Flag_Content)) + { + ui.gameFileView->setVisible(false); + return; + } + mGameFileProxyModel = new QSortFilterProxyModel(this); mGameFileProxyModel->setFilterRegExp(QString::number((int)ContentSelectorModel::ContentType_GameFile)); mGameFileProxyModel->setFilterRole (Qt::UserRole); mGameFileProxyModel->setSourceModel (mContentModel); - gameFileView->setPlaceholderText(QString("Select a game file...")); - gameFileView->setModel(mGameFileProxyModel); + ui.gameFileView->setPlaceholderText(QString("Select a game file...")); + ui.gameFileView->setModel(mGameFileProxyModel); - connect(gameFileView, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCurrentGameFileIndexChanged(int))); - connect(gameFileView, SIGNAL(currentIndexChanged(int)), this, SIGNAL(signalGameFileChanged(int))); + connect(ui.gameFileView, SIGNAL(currentIndexChanged(int)), this, SLOT (slotCurrentGameFileIndexChanged(int))); - gameFileView->setCurrentIndex(-1); - gameFileView->setCurrentIndex(0); + ui.gameFileView->setCurrentIndex(-1); } void ContentSelectorView::ContentSelector::buildAddonView() { + if (!isFlagged (Flag_Content)) + { + ui.addonView->setVisible(false); + return; + } + mAddonProxyModel = new QSortFilterProxyModel(this); mAddonProxyModel->setFilterRegExp (QString::number((int)ContentSelectorModel::ContentType_Addon)); mAddonProxyModel->setFilterRole (Qt::UserRole); mAddonProxyModel->setDynamicSortFilter (true); mAddonProxyModel->setSourceModel (mContentModel); - addonView->setModel(mAddonProxyModel); + ui.addonView->setModel(mAddonProxyModel); - connect(addonView, SIGNAL(clicked(const QModelIndex &)), this, SLOT(slotAddonTableItemClicked(const QModelIndex &))); + connect(ui.addonView, SIGNAL(clicked(const QModelIndex &)), this, SLOT(slotAddonTableItemClicked(const QModelIndex &))); } void ContentSelectorView::ContentSelector::buildProfilesView() { - profilesComboBox->setPlaceholderText(QString("Select a profile...")); - connect(profilesComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCurrentProfileIndexChanged(int))); + if (!isFlagged (Flag_Profile)) + { + ui.profileGroupBox->setVisible(false); + return; + } + + ui.profilesComboBox->setPlaceholderText(QString("Select a profile...")); + connect(ui.profilesComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCurrentProfileIndexChanged(int))); } -void ContentSelectorView::ContentSelector::updateViews() +void ContentSelectorView::ContentSelector::buildLoadAddonView() { - // Ensure the columns are hidden because sort() re-enables them - addonView->setColumnHidden(1, true); - addonView->setColumnHidden(2, true); - addonView->setColumnHidden(3, true); - addonView->setColumnHidden(4, true); - addonView->setColumnHidden(5, true); - addonView->setColumnHidden(6, true); - addonView->setColumnHidden(7, true); - addonView->setColumnHidden(8, true); - addonView->resizeColumnsToContents(); + if (!isFlagged (Flag_LoadAddon)) + { + ui.projectGroupBox->setVisible (false); + return; + } + + ui.projectCreateButton->setVisible (false); + // ui.projectButtonBox->setStandardButtons(QDialogButtonBox::Open | QDialogButtonBox::Cancel); + ui.projectGroupBox->setTitle (""); + + connect(ui.projectButtonBox, SIGNAL(accepted()), this, SIGNAL(accepted())); + connect(ui.projectButtonBox, SIGNAL(rejected()), this, SIGNAL(rejected())); } +void ContentSelectorView::ContentSelector::buildNewAddonView() +{ + if (!isFlagged (Flag_NewAddon)) + { + ui.profileGroupBox->setVisible (false); + return; + } + + mFileWidget = new CSVDoc::FileWidget (this); + mAdjusterWidget = new CSVDoc::AdjusterWidget (this); + + mFileWidget->setType(true); + mFileWidget->extensionLabelIsVisible(false); + + ui.fileWidgetFrame->layout()->addWidget(mFileWidget); + ui.adjusterWidgetFrame->layout()->addWidget(mAdjusterWidget); + + ui.projectButtonBox->setStandardButtons(QDialogButtonBox::Cancel); + ui.projectButtonBox->addButton(ui.projectCreateButton, QDialogButtonBox::ActionRole); + + connect (mFileWidget, SIGNAL (nameChanged (const QString&, bool)), + mAdjusterWidget, SLOT (setName (const QString&, bool))); + + connect (mAdjusterWidget, SIGNAL (stateChanged(bool)), this, SLOT (slotUpdateCreateButton(bool))); + + connect(ui.projectCreateButton, SIGNAL(clicked()), this, SIGNAL(accepted())); + connect(ui.projectButtonBox, SIGNAL(rejected()), this, SIGNAL(rejected())); +} + +QString ContentSelectorView::ContentSelector::filename() const +{ + QString filepath = ""; + + if (mAdjusterWidget) + filepath = QString::fromAscii(mAdjusterWidget->getPath().c_str()); + + return filepath; +} + +QStringList ContentSelectorView::ContentSelector::selectedFiles() const +{ + QStringList filePaths; + + if (mContentModel) + { + foreach (ContentSelectorModel::EsmFile *file, mContentModel->checkedItems()) + filePaths.append(file->path()); + } + + return filePaths; +} + + void ContentSelectorView::ContentSelector::addFiles(const QString &path) { - mContentModel->addFiles(path); - //mContentModel->sort(3); // Sort by date accessed - gameFileView->setCurrentIndex(-1); - mContentModel->uncheckAll(); + // if the model hasn't been instantiated, queue the path + if (!mInstance) + mFilePaths.append(path); + else + { + mInstance->mContentModel->addFiles(path); + mInstance->ui.gameFileView->setCurrentIndex(-1); + mInstance->mContentModel->uncheckAll(); + } } QStringList ContentSelectorView::ContentSelector::checkedItemsPaths() @@ -99,14 +221,14 @@ QStringList ContentSelectorView::ContentSelector::checkedItemsPaths() void ContentSelectorView::ContentSelector::slotCurrentProfileIndexChanged(int index) { - emit profileChanged(index); + emit signalProfileChanged(index); } void ContentSelectorView::ContentSelector::slotCurrentGameFileIndexChanged(int index) { static int oldIndex = -1; - QAbstractItemModel *const model = gameFileView->model(); + QAbstractItemModel *const model = ui.gameFileView->model(); QSortFilterProxyModel *proxy = dynamic_cast(model); if (proxy) @@ -122,16 +244,37 @@ void ContentSelectorView::ContentSelector::slotCurrentGameFileIndexChanged(int i if (proxy) proxy->setDynamicSortFilter(true); - emit signalGameFileChanged(true); + slotUpdateCreateButton(true); } void ContentSelectorView::ContentSelector::slotAddonTableItemClicked(const QModelIndex &index) { - QAbstractItemModel *const model = addonView->model(); - //QSortFilterProxyModel *proxy = dynamic_cast(model); + QAbstractItemModel *const model = ui.addonView->model(); if (model->data(index, Qt::CheckStateRole).toInt() == Qt::Unchecked) model->setData(index, Qt::Checked, Qt::CheckStateRole); else model->setData(index, Qt::Unchecked, Qt::CheckStateRole); } + +void ContentSelectorView::ContentSelector::slotUpdateOpenButton(const QStringList &items) +{ + QPushButton *openButton = ui.projectButtonBox->button(QDialogButtonBox::Open); + + if (!openButton) + return; + + openButton->setEnabled(!items.isEmpty()); +} + +void ContentSelectorView::ContentSelector::slotUpdateCreateButton(bool) +{ + //enable only if a game file is selected and the adjuster widget is non-empty + bool validGameFile = (ui.gameFileView->currentIndex() != -1); + bool validFilename = false; + + if (mAdjusterWidget) + validFilename = mAdjusterWidget->isValid(); + + ui.projectCreateButton->setEnabled(validGameFile && validFilename); +} diff --git a/components/contentselector/view/contentselector.hpp b/components/contentselector/view/contentselector.hpp index 5af53dc464..48c3ae103e 100644 --- a/components/contentselector/view/contentselector.hpp +++ b/components/contentselector/view/contentselector.hpp @@ -9,12 +9,33 @@ namespace ContentSelectorModel { class ContentModel; } class QSortFilterProxyModel; +namespace CSVDoc +{ + class FileWidget; + class AdjusterWidget; +} namespace ContentSelectorView { - class ContentSelector : public QDialog, protected Ui::DataFilesPage + enum SelectorFlags + { + Flag_Content = 0x01, // gamefile combobox & addon list view + Flag_NewAddon = 0x02, // enable project button box (Create/Cancel) and file/adjuster widgets + Flag_LoadAddon = 0x04, // enable project button box (Open/Cancel) + Flag_Profile = 0x08 // enable profile combo box + }; + + class ContentSelector : public QWidget { Q_OBJECT + unsigned char mFlags; + + static ContentSelector *mInstance; + static QStringList mFilePaths; + + CSVDoc::FileWidget *mFileWidget; + CSVDoc::AdjusterWidget *mAdjusterWidget; + protected: ContentSelectorModel::ContentModel *mContentModel; @@ -23,30 +44,46 @@ namespace ContentSelectorView public: - explicit ContentSelector(QWidget *parent = 0); + static void configure(QWidget *subject, unsigned char flags = Flag_Content); + static ContentSelector &instance(); + static void addFiles(const QString &path); - static ContentSelector &cast(QWidget *subject); //static constructor function for singleton performance. - - void addFiles(const QString &path); void setCheckState(QModelIndex index, QSortFilterProxyModel *model); QStringList checkedItemsPaths(); + QString filename() const; + QStringList selectedFiles() const; private: + explicit ContentSelector(QWidget *parent = 0, unsigned char flags = Flag_Content); + Ui::DataFilesPage ui; + void buildContentModel(); void buildGameFileView(); void buildAddonView(); void buildProfilesView(); + void buildNewAddonView(); + void buildLoadAddonView(); + + bool isFlagged(SelectorFlags flag) const; signals: - void profileChanged(int index); + void accepted(); + void rejected(); + + void signalProfileChanged(int index); void signalGameFileChanged(int value); + void signalCreateButtonClicked(); + private slots: - void updateViews(); + void slotCurrentProfileIndexChanged(int index); void slotCurrentGameFileIndexChanged(int index); void slotAddonTableItemClicked(const QModelIndex &index); + + void slotUpdateCreateButton (bool); + void slotUpdateOpenButton(const QStringList &items); }; } From 00c78a4aa1be9d465d8e7e625871bf02af3619c5 Mon Sep 17 00:00:00 2001 From: graffy76 Date: Tue, 1 Oct 2013 21:29:45 -0500 Subject: [PATCH 071/434] Implementing ContentSelector class in DataFilesPage Moved AdjusterWidget / FileWidget to ContentSelectorView --- apps/launcher/datafilespage.cpp | 340 ++++-------------- apps/launcher/datafilespage.hpp | 46 +-- apps/launcher/maindialog.cpp | 6 +- apps/opencs/CMakeLists.txt | 3 +- apps/opencs/editor.cpp | 4 +- apps/opencs/view/doc/filedialog.cpp | 11 +- apps/opencs/view/doc/filedialog.hpp | 2 +- apps/opencs/view/doc/newgame.cpp | 4 +- components/CMakeLists.txt | 1 + .../contentselector/view}/adjusterwidget.cpp | 0 .../contentselector/view}/adjusterwidget.hpp | 0 .../contentselector/view/contentselector.cpp | 169 ++++++++- .../contentselector/view/contentselector.hpp | 35 +- .../contentselector/view}/filewidget.cpp | 0 .../contentselector/view}/filewidget.hpp | 0 .../contentselector/view/profilescombobox.cpp | 3 + .../contentselector/view/profilescombobox.hpp | 2 + files/ui/datafilespage.ui | 49 ++- 18 files changed, 316 insertions(+), 359 deletions(-) rename {apps/opencs/view/doc => components/contentselector/view}/adjusterwidget.cpp (100%) rename {apps/opencs/view/doc => components/contentselector/view}/adjusterwidget.hpp (100%) rename {apps/opencs/view/doc => components/contentselector/view}/filewidget.cpp (100%) rename {apps/opencs/view/doc => components/contentselector/view}/filewidget.hpp (100%) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 828b2f2ba2..b298f8a141 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -19,7 +19,6 @@ #include "utils/textinputdialog.hpp" #include "components/contentselector/view/contentselector.hpp" -#include "components/contentselector/model/contentmodel.hpp" #include @@ -28,176 +27,59 @@ DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gam , mGameSettings(gameSettings) , mLauncherSettings(launcherSettings) { - setupUi(this); -/* - // QMetaObject::connectSlotsByName(this); + unsigned char flags; - projectGroupBox->hide(); + flags = ContentSelectorView::Flag_Content | ContentSelectorView::Flag_Profile; - // Create a dialog for the new profile name input - mNewProfileDialog = new TextInputDialog(tr("New Profile"), tr("Profile name:"), this); + ContentSelectorView::ContentSelector::configure(this, flags); - connect(mNewProfileDialog->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(updateOkButton(QString))); - - - - buildContentModel(); - buildGameFileView(); - buildAddonView(); - buildProfilesView(); - - - createActions(); setupDataFiles(); + ContentSelectorView::ContentSelector &cSelector = + ContentSelectorView::ContentSelector::instance(); - updateViews();*/ -} + connect (&cSelector, SIGNAL (signalProfileRenamed (QString, QString)), + this, SLOT (slotProfileRenamed (QString, QString))); -void DataFilesPage::buildContentModel() -{ - mContentModel = new ContentSelectorModel::ContentModel(); - connect(mContentModel, SIGNAL(layoutChanged()), this, SLOT(updateViews())); -} + connect (&cSelector, SIGNAL (signalProfileChanged (QString, QString)), + this, SLOT (slotProfileChanged (QString, QString))); -void DataFilesPage::buildGameFileView() -{ - mGameFileProxyModel = new QSortFilterProxyModel(this); - mGameFileProxyModel->setFilterRegExp(QString::number((int)ContentSelectorModel::ContentType_GameFile)); - mGameFileProxyModel->setFilterRole (Qt::UserRole); - mGameFileProxyModel->setSourceModel (mContentModel); + connect (&cSelector, SIGNAL (signalProfileDeleted (QString)), + this, SLOT (slotProfileDeleted (QString))); - gameFileView->setPlaceholderText(QString("Select a game file...")); - gameFileView->setModel(mGameFileProxyModel); - - connect(gameFileView, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCurrentGameFileIndexChanged(int))); - - gameFileView->setCurrentIndex(-1); - gameFileView->setCurrentIndex(0); -} - -void DataFilesPage::buildAddonView() -{ - mAddonProxyModel = new QSortFilterProxyModel(this); - mAddonProxyModel->setFilterRegExp (QString::number((int)ContentSelectorModel::ContentType_Addon)); - mAddonProxyModel->setFilterRole (Qt::UserRole); - mAddonProxyModel->setDynamicSortFilter (true); - mAddonProxyModel->setSourceModel (mContentModel); - - addonView->setModel(mAddonProxyModel); - - connect(addonView, SIGNAL(clicked(const QModelIndex &)), this, SLOT(slotAddonTableItemClicked(const QModelIndex &))); -} - -void DataFilesPage::buildProfilesView() -{ - profilesComboBox->setPlaceholderText(QString("Select a profile...")); - connect(profilesComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCurrentProfileIndexChanged(int))); -} - -void DataFilesPage::updateViews() -{ - // Ensure the columns are hidden because sort() re-enables them - addonView->setColumnHidden(1, true); - addonView->setColumnHidden(2, true); - addonView->setColumnHidden(3, true); - addonView->setColumnHidden(4, true); - addonView->setColumnHidden(5, true); - addonView->setColumnHidden(6, true); - addonView->setColumnHidden(7, true); - addonView->setColumnHidden(8, true); - addonView->resizeColumnsToContents(); -} - -void ContentSelectorView::ContentSelector::addFiles(const QString &path) -{ - // mContentModel->addFiles(path); - //mContentModel->sort(3); // Sort by date accessed - // ui.gameFileView->setCurrentIndex(-1); - // mContentModel->uncheckAll(); -} - -void DataFilesPage::createActions() -{ - // Add the actions to the toolbuttons - newProfileButton->setDefaultAction(newProfileAction); - deleteProfileButton->setDefaultAction(deleteProfileAction); -} - -void DataFilesPage::setupDataFiles() -{ - // Set the encoding to the one found in openmw.cfg or the default - //mContentSelector.setEncoding(mGameSettings.value(QString("encoding"), QString("win1252"))); - - QStringList paths = mGameSettings.getDataDirs(); - - foreach (const QString &path, paths) { - //mContentSelector. - mContentModel->addFiles(path); - } - - QString dataLocal = mGameSettings.getDataLocal(); - if (!dataLocal.isEmpty()) - //mContentSelector. - mContentModel->addFiles(dataLocal); - - // Sort by date accessed for now - //mContentSelector->sort(3); - - QStringList profiles = mLauncherSettings.subKeys(QString("Profiles/")); - QString profile = mLauncherSettings.value(QString("Profiles/currentprofile")); - - if (!profiles.isEmpty()) - profilesComboBox->addItems(profiles); - - // Add the current profile if empty - if (profilesComboBox->findText(profile) == -1 && !profile.isEmpty()) - profilesComboBox->addItem(profile); - - if (profilesComboBox->findText(QString("Default")) == -1) - profilesComboBox->addItem(QString("Default")); - - if (profile.isEmpty() || profile == QLatin1String("Default")) { - deleteProfileAction->setEnabled(false); - profilesComboBox->setEditEnabled(false); - profilesComboBox->setCurrentIndex(profilesComboBox->findText(QString("Default"))); - } else { - profilesComboBox->setEditEnabled(true); - profilesComboBox->setCurrentIndex(profilesComboBox->findText(profile)); - } - - // We do this here to prevent deletion of profiles when initializing the combobox - connect(profilesComboBox, SIGNAL(profileRenamed(QString,QString)), this, SLOT(profileRenamed(QString,QString))); - connect(profilesComboBox, SIGNAL(profileChanged(QString,QString)), this, SLOT(profileChanged(QString,QString))); - - loadSettings(); - - gameFileView->setCurrentIndex(-1); + connect (&cSelector, SIGNAL (signalProfileAdded ()), + this, SLOT (slotProfileAdded ())); } void DataFilesPage::loadSettings() { + QString profile = mLauncherSettings.value(QString("Profiles/currentprofile")); if (profile.isEmpty()) return; - // mContentSelector. - mContentModel->uncheckAll(); - - QStringList gameFiles = mLauncherSettings.values(QString("Profiles/") + profile + QString("/master"), Qt::MatchExactly); + QStringList files = mLauncherSettings.values(QString("Profiles/") + profile + QString("/master"), Qt::MatchExactly); QStringList addons = mLauncherSettings.values(QString("Profiles/") + profile + QString("/plugin"), Qt::MatchExactly); + + foreach (const QString &file, addons) + files.append(file); + + //ContentSelectorView::ContentSelector::instance().setCheckStates(files); } void DataFilesPage::saveSettings() { - if (mContentModel->rowCount() < 1) - return; + ContentSelectorModel::ContentFileList items = + ContentSelectorView::ContentSelector::instance().selectedFiles(); + + if (items.size() == 0) + return; QString profile = mLauncherSettings.value(QString("Profiles/currentprofile")); if (profile.isEmpty()) { - profile = profilesComboBox->currentText(); + profile = ContentSelectorView::ContentSelector::instance().getProfileText(); mLauncherSettings.setValue(QString("Profiles/currentprofile"), profile); } @@ -207,8 +89,6 @@ void DataFilesPage::saveSettings() mGameSettings.remove(QString("master")); mGameSettings.remove(QString("plugin")); - ContentSelectorModel::ContentFileList items = mContentModel->checkedItems(); - foreach(const ContentSelectorModel::EsmFile *item, items) { if (item->gameFiles().size() == 0) { @@ -223,109 +103,18 @@ void DataFilesPage::saveSettings() } -void DataFilesPage::updateOkButton(const QString &text) +void DataFilesPage::slotProfileDeleted (const QString &item) { - // We do this here because we need the profiles combobox text - if (text.isEmpty()) { - mNewProfileDialog->setOkButtonEnabled(false); - return; - } - - (profilesComboBox->findText(text) == -1) - ? mNewProfileDialog->setOkButtonEnabled(true) - : mNewProfileDialog->setOkButtonEnabled(false); + mLauncherSettings.remove(QString("Profiles/") + item + QString("/master")); + mLauncherSettings.remove(QString("Profiles/") + item + QString("/plugin")); } -void DataFilesPage::setProfilesComboBoxIndex(int index) +void DataFilesPage::slotProfileChanged(const QString &previous, const QString ¤t) { - profilesComboBox->setCurrentIndex(index); -} - -QAbstractItemModel* DataFilesPage::profilesComboBoxModel() -{ - return profilesComboBox->model(); -} - -int DataFilesPage::profilesComboBoxIndex() -{ - return profilesComboBox->currentIndex(); -} - -void DataFilesPage::on_newProfileAction_triggered() -{ - if (mNewProfileDialog->exec() == QDialog::Accepted) { - QString profile = mNewProfileDialog->lineEdit()->text(); - profilesComboBox->addItem(profile); - profilesComboBox->setCurrentIndex(profilesComboBox->findText(profile)); - } -} - -void DataFilesPage::on_deleteProfileAction_triggered() -{ - QString profile = profilesComboBox->currentText(); - - if (profile.isEmpty()) - return; - - QMessageBox msgBox(this); - msgBox.setWindowTitle(tr("Delete Profile")); - msgBox.setIcon(QMessageBox::Warning); - msgBox.setStandardButtons(QMessageBox::Cancel); - msgBox.setText(tr("Are you sure you want to delete %0?").arg(profile)); - - QAbstractButton *deleteButton = - msgBox.addButton(tr("Delete"), QMessageBox::ActionRole); - - msgBox.exec(); - - if (msgBox.clickedButton() == deleteButton) { - mLauncherSettings.remove(QString("Profiles/") + profile + QString("/master")); - mLauncherSettings.remove(QString("Profiles/") + profile + QString("/plugin")); - - // Remove the profile from the combobox - profilesComboBox->removeItem(profilesComboBox->findText(profile)); - } -} - -void DataFilesPage::setPluginsCheckstates(Qt::CheckState state) -{ - if (!addonView->selectionModel()->hasSelection()) { - return; - } - - QModelIndexList indexes = addonView->selectionModel()->selectedIndexes(); - - foreach (const QModelIndex &index, indexes) - { - if (!index.isValid()) - return; - - QModelIndex sourceIndex = mAddonProxyModel->mapToSource(index); - - if (!sourceIndex.isValid()) - return; - - //bool isChecked = ( state == Qt::Checked ); - - mContentModel->setData(sourceIndex, state, Qt::CheckStateRole); - } -} - -void DataFilesPage::profileChanged(const QString &previous, const QString ¤t) -{ - // Prevent the deletion of the default profile - if (current == QLatin1String("Default")) { - deleteProfileAction->setEnabled(false); - profilesComboBox->setEditEnabled(false); - } else { - deleteProfileAction->setEnabled(true); - profilesComboBox->setEditEnabled(true); - } - if (previous.isEmpty()) return; - if (profilesComboBox->findText(previous) == -1) + if (ContentSelectorView::ContentSelector::instance().getProfileIndex (previous) == -1) return; // Profile was deleted // Store the previous profile @@ -336,7 +125,7 @@ void DataFilesPage::profileChanged(const QString &previous, const QString &curre loadSettings(); } -void DataFilesPage::profileRenamed(const QString &previous, const QString ¤t) +void DataFilesPage::slotProfileRenamed(const QString &previous, const QString ¤t) { if (previous.isEmpty()) return; @@ -350,56 +139,55 @@ void DataFilesPage::profileRenamed(const QString &previous, const QString &curre mLauncherSettings.remove(QString("Profiles/") + previous + QString("/plugin")); // Remove the profile from the combobox - profilesComboBox->removeItem(profilesComboBox->findText(previous)); + ContentSelectorView::ContentSelector::instance().removeProfile (previous); loadSettings(); - } -//////////////////////////// -QStringList DataFilesPage::checkedItemsPaths() +void DataFilesPage::slotProfileAdded() { - QStringList itemPaths; + TextInputDialog newDialog (tr("New Profile"), tr("Profile name:"), this); - foreach( const ContentSelectorModel::EsmFile *file, mContentModel->checkedItems()) - itemPaths << file->path(); + // connect(mNewDialog->lineEdit(), SIGNAL(textChanged(QString)), + // this, SLOT(updateOkButton(QString))); - return itemPaths; + if (newDialog.exec() == QDialog::Accepted) + { + QString profile = newDialog.lineEdit()->text(); + + ContentSelectorView::ContentSelector + ::instance().addProfile(profile, true); + } } -void DataFilesPage::slotCurrentProfileIndexChanged(int index) +void DataFilesPage::setProfilesComboBoxIndex(int index) { - emit profileChanged(index); + ContentSelectorView::ContentSelector::instance().setProfileIndex(index); } -void DataFilesPage::slotCurrentGameFileIndexChanged(int index) +void DataFilesPage::setupDataFiles() { - static int oldIndex = -1; + ContentSelectorView::ContentSelector &cSelector = + ContentSelectorView::ContentSelector::instance(); - QAbstractItemModel *const model = gameFileView->model(); - QSortFilterProxyModel *proxy = dynamic_cast(model); + QStringList paths = mGameSettings.getDataDirs(); - if (proxy) - proxy->setDynamicSortFilter(false); + foreach (const QString &path, paths) + cSelector.addFiles(path); - if (oldIndex > -1) - model->setData(model->index(oldIndex, 0), false, Qt::UserRole + 1); + QString dataLocal = mGameSettings.getDataLocal(); - oldIndex = index; + if (!dataLocal.isEmpty()) + cSelector.addFiles(dataLocal); - model->setData(model->index(index, 0), true, Qt::UserRole + 1); + QStringList profiles = mLauncherSettings.subKeys(QString("Profiles/")); + QString profile = mLauncherSettings.value(QString("Profiles/currentprofile")); - if (proxy) - proxy->setDynamicSortFilter(true); -} - -void DataFilesPage::slotAddonTableItemClicked(const QModelIndex &index) -{ - QAbstractItemModel *const model = addonView->model(); - //QSortFilterProxyModel *proxy = dynamic_cast(model); - - if (model->data(index, Qt::CheckStateRole).toInt() == Qt::Unchecked) - model->setData(index, Qt::Checked, Qt::CheckStateRole); - else - model->setData(index, Qt::Unchecked, Qt::CheckStateRole); + + foreach (const QString &item, profiles) + cSelector.addProfile (item); + + cSelector.addProfile (profile, true); + + loadSettings(); } diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index 9c7b0538ed..6ed5d9ce97 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -4,9 +4,6 @@ #include #include -#include "ui_datafilespage.h" -#include "components/contentselector/view/contentselector.hpp" - class QSortFilterProxyModel; class QAbstractItemModel; class QAction; @@ -19,7 +16,7 @@ class LauncherSettings; namespace Files { struct ConfigurationManager; } -class DataFilesPage : public QWidget, private Ui::DataFilesPage +class DataFilesPage : public QWidget { Q_OBJECT @@ -36,58 +33,33 @@ signals: void profileChanged(int index); public slots: - void setProfilesComboBoxIndex(int index); + //void showContextMenu(const QPoint &point); - //void showContextMenu(const QPoint &point); - void profileChanged(const QString &previous, const QString ¤t); - void profileRenamed(const QString &previous, const QString ¤t); - void updateOkButton(const QString &text); - void updateViews(); - // Action slots - void on_newProfileAction_triggered(); - void on_deleteProfileAction_triggered(); private slots: + void slotProfileAdded(); + void slotProfileChanged(const QString &previous, const QString ¤t); + void slotProfileRenamed(const QString &previous, const QString ¤t); + void slotProfileDeleted(const QString &item); + void setProfilesComboBoxIndex(int index); + private: QMenu *mContextMenu; - //ContentSelectorView::ContentSelector mContentSelector; - ContentSelectorModel::ContentModel *mContentModel; + Files::ConfigurationManager &mCfgMgr; GameSettings &mGameSettings; LauncherSettings &mLauncherSettings; - TextInputDialog *mNewProfileDialog; - QSortFilterProxyModel *mGameFileProxyModel; - QSortFilterProxyModel *mAddonProxyModel; - void setPluginsCheckstates(Qt::CheckState state); - void createActions(); void setupDataFiles(); void setupConfig(); void readConfig(); void loadSettings(); - - ////////////////////////////////////// - void buildContentModel(); - void buildGameFileView(); - void buildAddonView(); - void buildProfilesView(); - - //void addFiles(const QString &path); - - QStringList checkedItemsPaths(); - -private slots: - void slotCurrentProfileIndexChanged(int index); - void slotCurrentGameFileIndexChanged(int index); - void slotAddonTableItemClicked(const QModelIndex &index); - - }; #endif diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 032f70916f..311e3a25cc 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -106,10 +106,10 @@ void MainDialog::createPages() mPlayPage = new PlayPage(this); mGraphicsPage = new GraphicsPage(mCfgMgr, mGraphicsSettings, this); mDataFilesPage = new DataFilesPage(mCfgMgr, mGameSettings, mLauncherSettings, this); - +/// reimplement datafilespage functions to provide access // Set the combobox of the play page to imitate the combobox on the datafilespage - mPlayPage->setProfilesComboBoxModel(mDataFilesPage->profilesComboBoxModel()); - mPlayPage->setProfilesComboBoxIndex(mDataFilesPage->profilesComboBoxIndex()); + // mPlayPage->setProfilesComboBoxModel(mDataFilesPage->profilesComboBoxModel()); + // mPlayPage->setProfilesComboBoxIndex(mDataFilesPage->profilesComboBoxIndex()); // Add the pages to the stacked widget pagesWidget->addWidget(mPlayPage); diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 00547a2ba6..9ae12c7a73 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -43,8 +43,7 @@ opencs_units_noqt (model/tools opencs_units (view/doc - viewmanager view operations operation subview startup filedialog newgame filewidget - adjusterwidget + viewmanager view operations operation subview startup filedialog newgame ) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index fc9168e2ee..401f3839f1 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -124,7 +124,7 @@ void CS::Editor::openFiles() { std::vector files; - foreach (const QString &path, mFileDialog.selectedFilepaths()) { + foreach (const QString &path, mFileDialog.selectedFilePaths()) { files.push_back(path.toStdString()); } @@ -139,7 +139,7 @@ void CS::Editor::createNewFile() { std::vector files; - foreach (const QString &path, mFileDialog.selectedFilepaths()) { + foreach (const QString &path, mFileDialog.selectedFilePaths()) { files.push_back(path.toStdString()); } diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index 5a97a7a26d..b1b72dc1fa 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -37,9 +37,16 @@ QString CSVDoc::FileDialog::filename() return ContentSelectorView::ContentSelector::instance().filename(); } -QStringList CSVDoc::FileDialog::selectedFilepaths() +QStringList CSVDoc::FileDialog::selectedFilePaths() { - return ContentSelectorView::ContentSelector::instance().selectedFiles(); + QStringList filePaths; + + foreach (ContentSelectorModel::EsmFile *file, ContentSelectorView::ContentSelector:: + instance().selectedFiles() ) + { + filePaths.append(file->fileName()); + } + return filePaths; } void CSVDoc::FileDialog::showDialog() diff --git a/apps/opencs/view/doc/filedialog.hpp b/apps/opencs/view/doc/filedialog.hpp index 88d408b5c6..acc35189dc 100644 --- a/apps/opencs/view/doc/filedialog.hpp +++ b/apps/opencs/view/doc/filedialog.hpp @@ -41,7 +41,7 @@ namespace CSVDoc void addFiles (const QString &path); QString filename(); - QStringList selectedFilepaths(); + QStringList selectedFilePaths(); private: diff --git a/apps/opencs/view/doc/newgame.cpp b/apps/opencs/view/doc/newgame.cpp index 98681c499d..265b983056 100644 --- a/apps/opencs/view/doc/newgame.cpp +++ b/apps/opencs/view/doc/newgame.cpp @@ -7,8 +7,8 @@ #include #include -#include "filewidget.hpp" -#include "adjusterwidget.hpp" +#include "components/contentselector/view/filewidget.hpp" +#include "components/contentselector/view/adjusterwidget.hpp" CSVDoc::NewGameDialogue::NewGameDialogue() { diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 7053bb973b..ebce4578bd 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -84,6 +84,7 @@ if(QT_QTGUI_LIBRARY AND QT_QTCORE_LIBRARY) model/naturalsort model/contentmodel view/profilescombobox view/comboboxlineedit view/lineedit view/contentselector + view/filewidget view/adjusterwidget ) include(${QT_USE_FILE}) diff --git a/apps/opencs/view/doc/adjusterwidget.cpp b/components/contentselector/view/adjusterwidget.cpp similarity index 100% rename from apps/opencs/view/doc/adjusterwidget.cpp rename to components/contentselector/view/adjusterwidget.cpp diff --git a/apps/opencs/view/doc/adjusterwidget.hpp b/components/contentselector/view/adjusterwidget.hpp similarity index 100% rename from apps/opencs/view/doc/adjusterwidget.hpp rename to components/contentselector/view/adjusterwidget.hpp diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index d9caea3bed..cb0774f680 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -1,7 +1,7 @@ #include "contentselector.hpp" -#include "../model/contentmodel.hpp" #include "../model/esmfile.hpp" +#include "lineedit.hpp" #include @@ -9,10 +9,11 @@ #include #include #include +#include #include -#include "../../../apps/opencs/view/doc/filewidget.hpp" -#include "../../../apps/opencs/view/doc/adjusterwidget.hpp" +#include "filewidget.hpp" +#include "adjusterwidget.hpp" ContentSelectorView::ContentSelector *ContentSelectorView::ContentSelector::mInstance = 0; QStringList ContentSelectorView::ContentSelector::mFilePaths; @@ -25,6 +26,7 @@ void ContentSelectorView::ContentSelector::configure(QWidget *subject, unsigned ContentSelectorView::ContentSelector& ContentSelectorView::ContentSelector::instance() { + assert(mInstance); return *mInstance; } @@ -33,6 +35,7 @@ ContentSelectorView::ContentSelector::ContentSelector(QWidget *parent, unsigned QWidget(parent), mFlags (flags), mAdjusterWidget (0), mFileWidget (0) { + ui.setupUi (this); parent->setLayout(new QGridLayout()); @@ -41,15 +44,23 @@ ContentSelectorView::ContentSelector::ContentSelector(QWidget *parent, unsigned buildContentModel(); buildGameFileView(); buildAddonView(); - buildProfilesView(); buildNewAddonView(); buildLoadAddonView(); + buildProfilesView(); /* //mContentModel->sort(3); // Sort by date accessed */ } +QString ContentSelectorView::ContentSelector::getNewProfileName() +{ + // Create a dialog for the new profile name input + //mNewProfileDialog = new TextInputDialog(tr("New Profile"), tr("Profile name:"), this); + + //connect(mNewProfileDialog->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(updateOkButton(QString))); + return ""; +} bool ContentSelectorView::ContentSelector::isFlagged(SelectorFlags flag) const { @@ -123,8 +134,17 @@ void ContentSelectorView::ContentSelector::buildProfilesView() return; } - ui.profilesComboBox->setPlaceholderText(QString("Select a profile...")); - connect(ui.profilesComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCurrentProfileIndexChanged(int))); + // Add the actions to the toolbuttons + ui.newProfileButton->setDefaultAction (ui.newProfileAction); + ui.deleteProfileButton->setDefaultAction (ui.deleteProfileAction); + + ui.profilesComboBox->addItem ("Default"); + ui.profilesComboBox->setPlaceholderText (QString("Select a profile...")); + + connect (ui.profilesComboBox, SIGNAL (currentIndexChanged(int)), this, SLOT(slotCurrentProfileIndexChanged(int))); + connect (ui.profilesComboBox, SIGNAL (profileRenamed(QString,QString)), this, SIGNAL(signalProfileRenamed(QString,QString))); + connect (ui.profilesComboBox, SIGNAL (profileChanged(QString,QString)), this, SIGNAL(signalProfileChanged(QString,QString))); + connect (ui.profilesComboBox, SIGNAL (signalProfileTextChanged(QString)), this, SLOT (slotProfileTextChanged (QString))); } void ContentSelectorView::ContentSelector::buildLoadAddonView() @@ -136,7 +156,6 @@ void ContentSelectorView::ContentSelector::buildLoadAddonView() } ui.projectCreateButton->setVisible (false); - // ui.projectButtonBox->setStandardButtons(QDialogButtonBox::Open | QDialogButtonBox::Cancel); ui.projectGroupBox->setTitle (""); connect(ui.projectButtonBox, SIGNAL(accepted()), this, SIGNAL(accepted())); @@ -172,6 +191,17 @@ void ContentSelectorView::ContentSelector::buildNewAddonView() connect(ui.projectButtonBox, SIGNAL(rejected()), this, SIGNAL(rejected())); } +void ContentSelectorView::ContentSelector::setCheckStates(const QStringList &list) +{ + if (list.isEmpty()) + return; + + mContentModel->uncheckAll(); + + foreach (const QString &file, list) + mContentModel->setCheckState(file, Qt::Checked); +} + QString ContentSelectorView::ContentSelector::filename() const { QString filepath = ""; @@ -182,7 +212,7 @@ QString ContentSelectorView::ContentSelector::filename() const return filepath; } -QStringList ContentSelectorView::ContentSelector::selectedFiles() const +QStringList ContentSelectorView::ContentSelector::selectedFilePaths() const { QStringList filePaths; @@ -195,6 +225,15 @@ QStringList ContentSelectorView::ContentSelector::selectedFiles() const return filePaths; } +ContentSelectorModel::ContentFileList + ContentSelectorView::ContentSelector::selectedFiles() const +{ + if (mContentModel) + return mContentModel->checkedItems(); + + return ContentSelectorModel::ContentFileList(); +} + void ContentSelectorView::ContentSelector::addFiles(const QString &path) { @@ -209,6 +248,55 @@ void ContentSelectorView::ContentSelector::addFiles(const QString &path) } } +void ContentSelectorView::ContentSelector::removeProfile(const QString &item) +{ + int idx = ui.profilesComboBox->findText(item); + + if (idx != -1) + ui.profilesComboBox->removeItem(idx); +} + +int ContentSelectorView::ContentSelector::getProfileIndex ( const QString &item) const +{ + return ui.profilesComboBox->findText (item); +} + +void ContentSelectorView::ContentSelector::setProfileIndex(int index) +{ + if (index >=0 && index < ui.profilesComboBox->count()) + ui.profilesComboBox->setCurrentIndex(index); +} + +void ContentSelectorView::ContentSelector::addProfile (const QString &item, bool setAsCurrent) +{ + if (item.isEmpty()) + return; + + if (ui.profilesComboBox->findText(item) == -1) + ui.profilesComboBox->addItem(item); + + if (setAsCurrent) + ui.profilesComboBox->setCurrentIndex(ui.profilesComboBox->findText(item)); + + enableProfilesComboBox(); +} + +QString ContentSelectorView::ContentSelector::getProfileText() const +{ + return ui.profilesComboBox->currentText(); +} + +void ContentSelectorView::ContentSelector::enableProfilesComboBox() +{ + if (!ui.profilesComboBox->isEnabled()) + ui.profilesComboBox->setEnabled(true); + + if (!ui.deleteProfileAction->isEnabled()) + ui.deleteProfileAction->setEnabled(true); + + ui.projectButtonBox->button(QDialogButtonBox::Open)->setEnabled (true); +} + QStringList ContentSelectorView::ContentSelector::checkedItemsPaths() { QStringList itemPaths; @@ -221,6 +309,12 @@ QStringList ContentSelectorView::ContentSelector::checkedItemsPaths() void ContentSelectorView::ContentSelector::slotCurrentProfileIndexChanged(int index) { + //don't allow deleting "Default" profile + bool success = (ui.profilesComboBox->itemText(index) == "Default"); + + ui.deleteProfileAction->setEnabled(success); + ui.profilesComboBox->setEditEnabled(success); + emit signalProfileChanged(index); } @@ -247,6 +341,14 @@ void ContentSelectorView::ContentSelector::slotCurrentGameFileIndexChanged(int i slotUpdateCreateButton(true); } +void ContentSelectorView::ContentSelector::slotProfileTextChanged(const QString &text) +{ + QPushButton *opnBtn = ui.projectButtonBox->button(QDialogButtonBox::Open); + + if (opnBtn->isEnabled()) + opnBtn->setEnabled (false); +} + void ContentSelectorView::ContentSelector::slotAddonTableItemClicked(const QModelIndex &index) { QAbstractItemModel *const model = ui.addonView->model(); @@ -257,16 +359,6 @@ void ContentSelectorView::ContentSelector::slotAddonTableItemClicked(const QMode model->setData(index, Qt::Unchecked, Qt::CheckStateRole); } -void ContentSelectorView::ContentSelector::slotUpdateOpenButton(const QStringList &items) -{ - QPushButton *openButton = ui.projectButtonBox->button(QDialogButtonBox::Open); - - if (!openButton) - return; - - openButton->setEnabled(!items.isEmpty()); -} - void ContentSelectorView::ContentSelector::slotUpdateCreateButton(bool) { //enable only if a game file is selected and the adjuster widget is non-empty @@ -278,3 +370,44 @@ void ContentSelectorView::ContentSelector::slotUpdateCreateButton(bool) ui.projectCreateButton->setEnabled(validGameFile && validFilename); } + + +void ContentSelectorView::ContentSelector::on_newProfileAction_triggered() +{ + emit signalProfileAdded(); +} + +void ContentSelectorView::ContentSelector::on_deleteProfileAction_triggered() +{ + QString profile = ui.profilesComboBox->currentText(); + + if (profile.isEmpty()) + return; + + QMessageBox msgBox(this); + msgBox.setWindowTitle(tr("Delete Profile")); + msgBox.setIcon(QMessageBox::Warning); + msgBox.setStandardButtons(QMessageBox::Cancel); + msgBox.setText(tr("Are you sure you want to delete %0?").arg(profile)); + + QAbstractButton *deleteButton = + msgBox.addButton(tr("Delete"), QMessageBox::ActionRole); + + msgBox.exec(); + + if (msgBox.clickedButton() != deleteButton) + return; + + // Remove the profile from the combobox + ui.profilesComboBox->removeItem(ui.profilesComboBox->findText(profile)); + + //signal for removal from model + emit signalProfileDeleted (profile); +} +/* +void ContentSelectorView::ContentSelector::slotUpdateOkButton(const QString &text) +{ + bool success = (ui.profilesComboBox->findText(text) == -1); + + mNewDialog->setOkButtonEnabled(success); +}*/ diff --git a/components/contentselector/view/contentselector.hpp b/components/contentselector/view/contentselector.hpp index 48c3ae103e..caf9cc670e 100644 --- a/components/contentselector/view/contentselector.hpp +++ b/components/contentselector/view/contentselector.hpp @@ -4,10 +4,10 @@ #include #include "ui_datafilespage.h" - -namespace ContentSelectorModel { class ContentModel; } +#include "../model/contentmodel.hpp" class QSortFilterProxyModel; +class TextInputDialog; namespace CSVDoc { @@ -36,6 +36,8 @@ namespace ContentSelectorView CSVDoc::FileWidget *mFileWidget; CSVDoc::AdjusterWidget *mAdjusterWidget; + TextInputDialog *mNewDialog; + protected: ContentSelectorModel::ContentModel *mContentModel; @@ -43,19 +45,28 @@ namespace ContentSelectorView QSortFilterProxyModel *mAddonProxyModel; public: + explicit ContentSelector(QWidget *parent = 0, unsigned char flags = Flag_Content); static void configure(QWidget *subject, unsigned char flags = Flag_Content); static ContentSelector &instance(); static void addFiles(const QString &path); - void setCheckState(QModelIndex index, QSortFilterProxyModel *model); + void setCheckStates (const QStringList &list); QStringList checkedItemsPaths(); + ContentSelectorModel::ContentFileList *CheckedItems(); + QString filename() const; - QStringList selectedFiles() const; + ContentSelectorModel::ContentFileList selectedFiles() const; + QStringList selectedFilePaths() const; + + void addProfile (const QString &item, bool setAsCurrent = false); + void removeProfile (const QString &item); + int getProfileIndex (const QString &item) const; + void setProfileIndex (int index); + QString getProfileText() const; private: - explicit ContentSelector(QWidget *parent = 0, unsigned char flags = Flag_Content); Ui::DataFilesPage ui; void buildContentModel(); @@ -66,6 +77,8 @@ namespace ContentSelectorView void buildLoadAddonView(); bool isFlagged(SelectorFlags flag) const; + QString getNewProfileName(); + void enableProfilesComboBox(); signals: void accepted(); @@ -76,14 +89,24 @@ namespace ContentSelectorView void signalCreateButtonClicked(); + void signalProfileRenamed(QString,QString); + void signalProfileChanged(QString,QString); + void signalProfileDeleted(QString); + void signalProfileAdded(); + private slots: + void slotProfileTextChanged (const QString &text); void slotCurrentProfileIndexChanged(int index); void slotCurrentGameFileIndexChanged(int index); void slotAddonTableItemClicked(const QModelIndex &index); void slotUpdateCreateButton (bool); - void slotUpdateOpenButton(const QStringList &items); + // void slotUpdateOpenButton(const QStringList &items); + + // Action slots + void on_newProfileAction_triggered(); + void on_deleteProfileAction_triggered(); }; } diff --git a/apps/opencs/view/doc/filewidget.cpp b/components/contentselector/view/filewidget.cpp similarity index 100% rename from apps/opencs/view/doc/filewidget.cpp rename to components/contentselector/view/filewidget.cpp diff --git a/apps/opencs/view/doc/filewidget.hpp b/components/contentselector/view/filewidget.hpp similarity index 100% rename from apps/opencs/view/doc/filewidget.hpp rename to components/contentselector/view/filewidget.hpp diff --git a/components/contentselector/view/profilescombobox.cpp b/components/contentselector/view/profilescombobox.cpp index cb0ba7b77e..29001189d0 100644 --- a/components/contentselector/view/profilescombobox.cpp +++ b/components/contentselector/view/profilescombobox.cpp @@ -45,6 +45,9 @@ void ContentSelectorView::ProfilesComboBox::setEditEnabled(bool editable) connect(lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(slotTextChanged(QString))); + + connect (lineEdit(), SIGNAL(textChanged(QString)), this, + SIGNAL (signalProfileTextChanged (QString))); } void ContentSelectorView::ProfilesComboBox::slotTextChanged(const QString &text) diff --git a/components/contentselector/view/profilescombobox.hpp b/components/contentselector/view/profilescombobox.hpp index d81c1e6a5a..560c42c10f 100644 --- a/components/contentselector/view/profilescombobox.hpp +++ b/components/contentselector/view/profilescombobox.hpp @@ -17,10 +17,12 @@ namespace ContentSelectorView void setPlaceholderText (const QString &text); signals: + void signalProfileTextChanged (const QString &item); void profileChanged(const QString &previous, const QString ¤t); void profileRenamed(const QString &oldName, const QString &newName); private slots: + void slotEditingFinished(); void slotIndexChanged(int index); void slotTextChanged(const QString &text); diff --git a/files/ui/datafilespage.ui b/files/ui/datafilespage.ui index 9494077591..73d7a4902e 100644 --- a/files/ui/datafilespage.ui +++ b/files/ui/datafilespage.ui @@ -7,7 +7,7 @@ 0 0 518 - 313 + 424 @@ -26,6 +26,9 @@ Content + + 3 + @@ -116,6 +119,18 @@ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + 3 + + + 6 + + + 6 + + + 0 + @@ -143,18 +158,17 @@ - - - - QDialogButtonBox::Cancel|QDialogButtonBox::Open - - - false + + + 0 + 0 + + Create @@ -209,7 +223,7 @@ 3 - 9 + 6 0 @@ -220,7 +234,7 @@ - true + false @@ -228,6 +242,11 @@ 0 + + + Default + + @@ -262,6 +281,13 @@ + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Open + + + @@ -280,6 +306,9 @@ + + false + From a5a0f615330e9593515153bee28430519c0ac57f Mon Sep 17 00:00:00 2001 From: graffy76 Date: Tue, 1 Oct 2013 22:36:49 -0500 Subject: [PATCH 072/434] Fixed missing profiles combobox --- apps/launcher/datafilespage.cpp | 2 +- components/contentselector/view/contentselector.cpp | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index b298f8a141..eee530672d 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -65,7 +65,7 @@ void DataFilesPage::loadSettings() foreach (const QString &file, addons) files.append(file); - //ContentSelectorView::ContentSelector::instance().setCheckStates(files); + ContentSelectorView::ContentSelector::instance().setCheckStates(files); } void DataFilesPage::saveSettings() diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index cb0774f680..54199626e0 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -129,10 +129,7 @@ void ContentSelectorView::ContentSelector::buildAddonView() void ContentSelectorView::ContentSelector::buildProfilesView() { if (!isFlagged (Flag_Profile)) - { - ui.profileGroupBox->setVisible(false); return; - } // Add the actions to the toolbuttons ui.newProfileButton->setDefaultAction (ui.newProfileAction); @@ -145,6 +142,8 @@ void ContentSelectorView::ContentSelector::buildProfilesView() connect (ui.profilesComboBox, SIGNAL (profileRenamed(QString,QString)), this, SIGNAL(signalProfileRenamed(QString,QString))); connect (ui.profilesComboBox, SIGNAL (profileChanged(QString,QString)), this, SIGNAL(signalProfileChanged(QString,QString))); connect (ui.profilesComboBox, SIGNAL (signalProfileTextChanged(QString)), this, SLOT (slotProfileTextChanged (QString))); + + ui.profileGroupBox->setVisible (true); } void ContentSelectorView::ContentSelector::buildLoadAddonView() From 217a4d75b4118d8a785f93f36852e6713a0d67c3 Mon Sep 17 00:00:00 2001 From: graffy76 Date: Sun, 6 Oct 2013 21:13:47 -0500 Subject: [PATCH 073/434] Implemented profile function in launcher datafiles page Implemented dependency sorting to ensure dependent files appear latest in the list. --- apps/launcher/CMakeLists.txt | 4 - apps/launcher/datafilespage.cpp | 173 +++++++++--------- apps/launcher/datafilespage.hpp | 22 ++- apps/launcher/graphicspage.cpp | 1 + apps/launcher/maindialog.cpp | 30 ++- apps/launcher/playpage.cpp | 12 +- apps/launcher/playpage.hpp | 7 +- apps/opencs/view/doc/filedialog.cpp | 2 +- components/CMakeLists.txt | 2 + .../contentselector/model/contentmodel.cpp | 53 +++++- .../contentselector/model/contentmodel.hpp | 3 +- .../contentselector/view/contentselector.cpp | 162 ++++++++-------- .../contentselector/view/contentselector.hpp | 27 ++- .../contentselector/view/profilescombobox.cpp | 10 +- .../contentselector/view/profilescombobox.hpp | 10 +- .../contentselector/view}/textinputdialog.cpp | 23 ++- .../contentselector/view}/textinputdialog.hpp | 15 +- files/ui/datafilespage.ui | 7 +- 18 files changed, 311 insertions(+), 252 deletions(-) rename {apps/launcher/utils => components/contentselector/view}/textinputdialog.cpp (75%) rename {apps/launcher/utils => components/contentselector/view}/textinputdialog.hpp (78%) diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index 92cabffffd..49dedd8290 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -11,7 +11,6 @@ set(LAUNCHER settings/launchersettings.cpp utils/checkablemessagebox.cpp - utils/textinputdialog.cpp ${CMAKE_SOURCE_DIR}/files/launcher/launcher.rc ) @@ -32,8 +31,6 @@ set(LAUNCHER_HEADER settings/settingsbase.hpp utils/checkablemessagebox.hpp - utils/textinputdialog.hpp - ) if(NOT WIN32) LIST(APPEND LAUNCHER_HEADER unshieldthread.hpp) @@ -49,7 +46,6 @@ set(LAUNCHER_HEADER_MOC textslotmsgbox.hpp utils/checkablemessagebox.hpp - utils/textinputdialog.hpp ) if(NOT WIN32) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index eee530672d..a705ae37d8 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -17,177 +17,182 @@ #include "settings/gamesettings.hpp" #include "settings/launchersettings.hpp" -#include "utils/textinputdialog.hpp" #include "components/contentselector/view/contentselector.hpp" -#include - DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gameSettings, LauncherSettings &launcherSettings, QWidget *parent) : mCfgMgr(cfg) , mGameSettings(gameSettings) , mLauncherSettings(launcherSettings) + , QWidget(parent) { - unsigned char flags; + setObjectName ("DataFilesPage"); - flags = ContentSelectorView::Flag_Content | ContentSelectorView::Flag_Profile; + unsigned char flags; - ContentSelectorView::ContentSelector::configure(this, flags); + flags = ContentSelectorView::Flag_Content | ContentSelectorView::Flag_Profile; + + ContentSelectorView::ContentSelector::configure(this, flags); + mSelector = &ContentSelectorView::ContentSelector::instance(); setupDataFiles(); - ContentSelectorView::ContentSelector &cSelector = - ContentSelectorView::ContentSelector::instance(); - connect (&cSelector, SIGNAL (signalProfileRenamed (QString, QString)), + connect (mSelector, SIGNAL (signalProfileRenamed (QString, QString)), this, SLOT (slotProfileRenamed (QString, QString))); - connect (&cSelector, SIGNAL (signalProfileChanged (QString, QString)), - this, SLOT (slotProfileChanged (QString, QString))); + connect (mSelector, SIGNAL (signalProfileChangedByUser (QString, QString)), + this, SLOT (slotProfileChangedByUser (QString, QString))); - connect (&cSelector, SIGNAL (signalProfileDeleted (QString)), + connect (mSelector, SIGNAL (signalProfileDeleted (QString)), this, SLOT (slotProfileDeleted (QString))); - connect (&cSelector, SIGNAL (signalProfileAdded ()), - this, SLOT (slotProfileAdded ())); + connect (mSelector, SIGNAL (signalAddNewProfile (QString)), + this, SLOT (slotAddNewProfile (QString))); } void DataFilesPage::loadSettings() { + QString profileName = mSelector->getProfileText(); - QString profile = mLauncherSettings.value(QString("Profiles/currentprofile")); + QStringList files = mLauncherSettings.values(QString("Profiles/") + profileName + QString("/game"), Qt::MatchExactly); + QStringList addons = mLauncherSettings.values(QString("Profiles/") + profileName + QString("/addon"), Qt::MatchExactly); - if (profile.isEmpty()) - return; + mSelector->clearCheckStates(); - QStringList files = mLauncherSettings.values(QString("Profiles/") + profile + QString("/master"), Qt::MatchExactly); - QStringList addons = mLauncherSettings.values(QString("Profiles/") + profile + QString("/plugin"), Qt::MatchExactly); + if (files.size() > 0) + mSelector->setGameFile(files.at(0)); + else + mSelector->setGameFile(); - foreach (const QString &file, addons) - files.append(file); - - ContentSelectorView::ContentSelector::instance().setCheckStates(files); + mSelector->setCheckStates(addons); } -void DataFilesPage::saveSettings() +void DataFilesPage::saveSettings(const QString &profile) { - ContentSelectorModel::ContentFileList items = - ContentSelectorView::ContentSelector::instance().selectedFiles(); + QString profileName = profile; - if (items.size() == 0) - return; + if (profileName.isEmpty()) + profileName = mSelector->getProfileText(); - QString profile = mLauncherSettings.value(QString("Profiles/currentprofile")); + //retrieve the files selected for the profile + ContentSelectorModel::ContentFileList items = mSelector->selectedFiles(); - if (profile.isEmpty()) { - profile = ContentSelectorView::ContentSelector::instance().getProfileText(); - mLauncherSettings.setValue(QString("Profiles/currentprofile"), profile); - } + removeProfile (profileName); - mLauncherSettings.remove(QString("Profiles/") + profile + QString("/master")); - mLauncherSettings.remove(QString("Profiles/") + profile + QString("/plugin")); + mGameSettings.remove(QString("game")); + mGameSettings.remove(QString("addon")); - mGameSettings.remove(QString("master")); - mGameSettings.remove(QString("plugin")); + //set the value of the current profile (not necessarily the profile being saved!) + mLauncherSettings.setValue(QString("Profiles/currentprofile"), mSelector->getProfileText()); foreach(const ContentSelectorModel::EsmFile *item, items) { if (item->gameFiles().size() == 0) { - mLauncherSettings.setMultiValue(QString("Profiles/") + profile + QString("/master"), item->fileName()); - mGameSettings.setMultiValue(QString("master"), item->fileName()); - + mLauncherSettings.setMultiValue(QString("Profiles/") + profileName + QString("/game"), item->fileName()); + mGameSettings.setMultiValue(QString("game"), item->fileName()); } else { - mLauncherSettings.setMultiValue(QString("Profiles/") + profile + QString("/plugin"), item->fileName()); - mGameSettings.setMultiValue(QString("plugin"), item->fileName()); + mLauncherSettings.setMultiValue(QString("Profiles/") + profileName + QString("/addon"), item->fileName()); + mGameSettings.setMultiValue(QString("addon"), item->fileName()); } } } -void DataFilesPage::slotProfileDeleted (const QString &item) +void DataFilesPage::removeProfile(const QString &profile) { - mLauncherSettings.remove(QString("Profiles/") + item + QString("/master")); - mLauncherSettings.remove(QString("Profiles/") + item + QString("/plugin")); + mLauncherSettings.remove(QString("Profiles/") + profile + QString("/game")); + mLauncherSettings.remove(QString("Profiles/") + profile + QString("/addon")); } -void DataFilesPage::slotProfileChanged(const QString &previous, const QString ¤t) +void DataFilesPage::changeProfiles(const QString &previous, const QString ¤t, bool savePrevious) { - if (previous.isEmpty()) + //abort if no change (typically a duplicate signal) + if (previous == current) return; - if (ContentSelectorView::ContentSelector::instance().getProfileIndex (previous) == -1) - return; // Profile was deleted + int index = -1; - // Store the previous profile - mLauncherSettings.setValue(QString("Profiles/currentprofile"), previous); - saveSettings(); - mLauncherSettings.setValue(QString("Profiles/currentprofile"), current); + if (!previous.isEmpty()) + index = mSelector->getProfileIndex(previous); + + // Store the previous profile if it exists + if ( (index != -1) && savePrevious) + saveSettings(previous); loadSettings(); } +void DataFilesPage::slotAddNewProfile(const QString &profile) +{ + saveSettings(); + mSelector->clearCheckStates(); + mSelector->addProfile(profile, true); + mSelector->setGameFile(); + saveSettings(); + + emit signalProfileChanged(mSelector->getProfileIndex(profile)); +} + +void DataFilesPage::slotProfileDeleted (const QString &item) +{ + removeProfile (item); +} + +void DataFilesPage::slotProfileChangedByUser(const QString &previous, const QString ¤t) +{ + changeProfiles(previous, current); + emit signalProfileChanged(mSelector->getProfileIndex(current)); +} + void DataFilesPage::slotProfileRenamed(const QString &previous, const QString ¤t) { if (previous.isEmpty()) return; // Save the new profile name - mLauncherSettings.setValue(QString("Profiles/currentprofile"), current); saveSettings(); // Remove the old one - mLauncherSettings.remove(QString("Profiles/") + previous + QString("/master")); - mLauncherSettings.remove(QString("Profiles/") + previous + QString("/plugin")); - - // Remove the profile from the combobox - ContentSelectorView::ContentSelector::instance().removeProfile (previous); + removeProfile (previous); loadSettings(); } -void DataFilesPage::slotProfileAdded() +void DataFilesPage::slotProfileChanged(int index) { - TextInputDialog newDialog (tr("New Profile"), tr("Profile name:"), this); - - // connect(mNewDialog->lineEdit(), SIGNAL(textChanged(QString)), - // this, SLOT(updateOkButton(QString))); - - if (newDialog.exec() == QDialog::Accepted) - { - QString profile = newDialog.lineEdit()->text(); - - ContentSelectorView::ContentSelector - ::instance().addProfile(profile, true); - } -} - -void DataFilesPage::setProfilesComboBoxIndex(int index) -{ - ContentSelectorView::ContentSelector::instance().setProfileIndex(index); + mSelector->setProfile(index); } void DataFilesPage::setupDataFiles() { - ContentSelectorView::ContentSelector &cSelector = - ContentSelectorView::ContentSelector::instance(); - QStringList paths = mGameSettings.getDataDirs(); foreach (const QString &path, paths) - cSelector.addFiles(path); + mSelector->addFiles(path); QString dataLocal = mGameSettings.getDataLocal(); if (!dataLocal.isEmpty()) - cSelector.addFiles(dataLocal); + mSelector->addFiles(dataLocal); QStringList profiles = mLauncherSettings.subKeys(QString("Profiles/")); QString profile = mLauncherSettings.value(QString("Profiles/currentprofile")); foreach (const QString &item, profiles) - cSelector.addProfile (item); + mSelector->addProfile (item); - cSelector.addProfile (profile, true); + mSelector->addProfile (profile, true); loadSettings(); } + +QAbstractItemModel *DataFilesPage::profilesModel() const +{ + return mSelector->profilesModel(); +} + +int DataFilesPage::profilesIndex() const +{ + return mSelector->getProfileIndex(mSelector->getProfileText()); +} diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index 6ed5d9ce97..47e28493d0 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -15,22 +15,26 @@ class LauncherSettings; namespace Files { struct ConfigurationManager; } +namespace ContentSelectorView { class ContentSelector; } class DataFilesPage : public QWidget { Q_OBJECT + ContentSelectorView::ContentSelector *mSelector; + public: DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gameSettings, LauncherSettings &launcherSettings, QWidget *parent = 0); - QAbstractItemModel* profilesComboBoxModel(); - int profilesComboBoxIndex(); + QAbstractItemModel* profilesModel() const; + int profilesIndex() const; void writeConfig(QString profile = QString()); - void saveSettings(); + void saveSettings(const QString &profile = ""); + void loadSettings(); signals: - void profileChanged(int index); + void signalProfileChanged(int index); public slots: //void showContextMenu(const QPoint &point); @@ -38,11 +42,11 @@ public slots: private slots: - void slotProfileAdded(); - void slotProfileChanged(const QString &previous, const QString ¤t); + void slotAddNewProfile(const QString &profile); + void slotProfileChangedByUser(const QString &previous, const QString ¤t); + void slotProfileChanged(int); void slotProfileRenamed(const QString &previous, const QString ¤t); void slotProfileDeleted(const QString &item); - void setProfilesComboBoxIndex(int index); private: @@ -58,8 +62,8 @@ private: void setupDataFiles(); void setupConfig(); void readConfig(); - - void loadSettings(); + void removeProfile (const QString &profile); + void changeProfiles(const QString &previous, const QString ¤t, bool savePrevious = true); }; #endif diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index 4d9ce14d6d..2c6c711ea9 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -38,6 +38,7 @@ GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, GraphicsSettings &g , mGraphicsSettings(graphicsSetting) , QWidget(parent) { + setObjectName ("GraphicsPage"); setupUi(this); // Set the maximum res we can set in windowed mode diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 311e3a25cc..fe9ca141ec 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -106,10 +106,10 @@ void MainDialog::createPages() mPlayPage = new PlayPage(this); mGraphicsPage = new GraphicsPage(mCfgMgr, mGraphicsSettings, this); mDataFilesPage = new DataFilesPage(mCfgMgr, mGameSettings, mLauncherSettings, this); -/// reimplement datafilespage functions to provide access + // Set the combobox of the play page to imitate the combobox on the datafilespage - // mPlayPage->setProfilesComboBoxModel(mDataFilesPage->profilesComboBoxModel()); - // mPlayPage->setProfilesComboBoxIndex(mDataFilesPage->profilesComboBoxIndex()); + mPlayPage->setProfilesModel(mDataFilesPage->profilesModel()); + mPlayPage->setProfilesIndex(mDataFilesPage->profilesIndex()); // Add the pages to the stacked widget pagesWidget->addWidget(mPlayPage); @@ -121,8 +121,8 @@ void MainDialog::createPages() connect(mPlayPage, SIGNAL(playButtonClicked()), this, SLOT(play())); - connect(mPlayPage, SIGNAL(profileChanged(int)), mDataFilesPage, SLOT(setProfilesComboBoxIndex(int))); - connect(mDataFilesPage, SIGNAL(profileChanged(int)), mPlayPage, SLOT(setProfilesComboBoxIndex(int))); + connect(mPlayPage, SIGNAL(signalProfileChanged(int)), mDataFilesPage, SLOT(slotProfileChanged(int))); + connect(mDataFilesPage, SIGNAL(signalProfileChanged(int)), mPlayPage, SLOT(setProfilesIndex(int))); } @@ -316,7 +316,25 @@ void MainDialog::changePage(QListWidgetItem *current, QListWidgetItem *previous) if (!current) current = previous; - pagesWidget->setCurrentIndex(iconWidget->row(current)); + int currentIndex = iconWidget->row(current); + int previousIndex = iconWidget->row(previous); + + pagesWidget->setCurrentIndex(currentIndex); + + DataFilesPage *previousPage = dynamic_cast(pagesWidget->widget(previousIndex)); + DataFilesPage *currentPage = dynamic_cast(pagesWidget->widget(currentIndex)); + + //special call to update/save data files page list view when it's displayed/hidden. + if (previousPage) + { + if (previousPage->objectName() == "DataFilesPage") + previousPage->saveSettings(); + } + else if (currentPage) + { + if (currentPage->objectName() == "DataFilesPage") + currentPage->loadSettings(); + } } bool MainDialog::setupLauncherSettings() diff --git a/apps/launcher/playpage.cpp b/apps/launcher/playpage.cpp index 46900c5958..fc1ed1c69b 100644 --- a/apps/launcher/playpage.cpp +++ b/apps/launcher/playpage.cpp @@ -8,6 +8,7 @@ PlayPage::PlayPage(QWidget *parent) : QWidget(parent) { + setObjectName ("PlayPage"); setupUi(this); // Hacks to get the stylesheet look properly @@ -17,26 +18,21 @@ PlayPage::PlayPage(QWidget *parent) : QWidget(parent) #endif profilesComboBox->setView(new QListView()); - connect(profilesComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCurrentIndexChanged(int))); + connect(profilesComboBox, SIGNAL(activated(int)), this, SIGNAL (signalProfileChanged(int))); connect(playButton, SIGNAL(clicked()), this, SLOT(slotPlayClicked())); } -void PlayPage::setProfilesComboBoxModel(QAbstractItemModel *model) +void PlayPage::setProfilesModel(QAbstractItemModel *model) { profilesComboBox->setModel(model); } -void PlayPage::setProfilesComboBoxIndex(int index) +void PlayPage::setProfilesIndex(int index) { profilesComboBox->setCurrentIndex(index); } -void PlayPage::slotCurrentIndexChanged(int index) -{ - emit profileChanged(index); -} - void PlayPage::slotPlayClicked() { emit playButtonClicked(); diff --git a/apps/launcher/playpage.hpp b/apps/launcher/playpage.hpp index 4306396bd2..42edfadb18 100644 --- a/apps/launcher/playpage.hpp +++ b/apps/launcher/playpage.hpp @@ -15,17 +15,16 @@ class PlayPage : public QWidget, private Ui::PlayPage public: PlayPage(QWidget *parent = 0); - void setProfilesComboBoxModel(QAbstractItemModel *model); + void setProfilesModel(QAbstractItemModel *model); signals: - void profileChanged(int index); + void signalProfileChanged(int index); void playButtonClicked(); public slots: - void setProfilesComboBoxIndex(int index); + void setProfilesIndex(int index); private slots: - void slotCurrentIndexChanged(int index); void slotPlayClicked(); diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index b1b72dc1fa..efa31100ab 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -34,7 +34,7 @@ void CSVDoc::FileDialog::addFiles(const QString &path) QString CSVDoc::FileDialog::filename() { - return ContentSelectorView::ContentSelector::instance().filename(); + return ContentSelectorView::ContentSelector::instance().projectFilename(); } QStringList CSVDoc::FileDialog::selectedFilePaths() diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index ebce4578bd..103c6f4126 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -85,6 +85,8 @@ if(QT_QTGUI_LIBRARY AND QT_QTCORE_LIBRARY) view/profilescombobox view/comboboxlineedit view/lineedit view/contentselector view/filewidget view/adjusterwidget + view/textinputdialog + ) include(${QT_USE_FILE}) diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index db6431810d..a9796a1fbf 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -391,7 +391,6 @@ bool ContentSelectorModel::ContentModel::canBeChecked(const EsmFile *file) const return true; } } - return false; } @@ -448,6 +447,39 @@ void ContentSelectorModel::ContentModel::addFiles(const QString &path) } delete decoder; + + sortFiles(); +} + +void ContentSelectorModel::ContentModel::sortFiles() +{ + //first, sort the model such that all dependencies are ordered upstream (gamefile) first. + bool movedFiles = true; + int fileCount = mFiles.size(); + + //Dependency sort + //iterate until no sorting of files occurs + while (movedFiles) + { + movedFiles = false; + //iterate each file, obtaining a reference to it's gamefiles list + for (int i = 0; i < fileCount; i++) + { + const QStringList &gamefiles = mFiles.at(i)->gameFiles(); + //iterate each file after the current file, verifying that none of it's + //dependencies appear. + for (int j = i + 1; j < fileCount; j++) + { + if (gamefiles.contains(mFiles.at(j)->fileName())) + { + mFiles.move(j, i); + movedFiles = true; + } + } + if (movedFiles) + break; + } + } } bool ContentSelectorModel::ContentModel::isChecked(const QString& name) const @@ -460,6 +492,7 @@ bool ContentSelectorModel::ContentModel::isChecked(const QString& name) const void ContentSelectorModel::ContentModel::setCheckState(const QString &name, bool checkState) { + if (name.isEmpty()) return; @@ -469,9 +502,14 @@ void ContentSelectorModel::ContentModel::setCheckState(const QString &name, bool state = Qt::Checked; mCheckStates[name] = state; + emit dataChanged(indexFromItem(item(name)), indexFromItem(item(name))); const EsmFile *file = item(name); + if (file->isGameFile()) + emit dataChanged (index(0,0), index(rowCount()-1,0)); + + //if we're checking an item, ensure all "upstream" files (dependencies) are checked as well. if (state == Qt::Checked) { foreach (const QString &upstreamName, file->gameFiles()) @@ -482,24 +520,23 @@ void ContentSelectorModel::ContentModel::setCheckState(const QString &name, bool continue; if (!isChecked(upstreamName)) - { mCheckStates[upstreamName] = Qt::Checked; - emit dataChanged(indexFromItem(upstreamFile), indexFromItem(upstreamFile)); - } + + emit dataChanged(indexFromItem(upstreamFile), indexFromItem(upstreamFile)); } } - else if (state == Qt::Unchecked) + //otherwise, if we're unchecking an item (or the file is a game file) ensure all downstream files are unchecked. + if (state == Qt::Unchecked) { foreach (const EsmFile *downstreamFile, mFiles) { if (downstreamFile->gameFiles().contains(name)) { if (mCheckStates.contains(downstreamFile->fileName())) - { mCheckStates[downstreamFile->fileName()] = Qt::Unchecked; - emit dataChanged(indexFromItem(downstreamFile), indexFromItem(downstreamFile)); - } + + emit dataChanged(indexFromItem(downstreamFile), indexFromItem(downstreamFile)); } } } diff --git a/components/contentselector/model/contentmodel.hpp b/components/contentselector/model/contentmodel.hpp index a2a57f850a..feea3643b3 100644 --- a/components/contentselector/model/contentmodel.hpp +++ b/components/contentselector/model/contentmodel.hpp @@ -56,19 +56,20 @@ namespace ContentSelectorModel EsmFile *item(int row); bool canBeChecked(const EsmFile *file) const; + void sortFiles(); ContentFileList mFiles; QHash mCheckStates; QTextCodec *mCodec; public: + QString mMimeType; QStringList mMimeTypes; int mColumnCount; Qt::ItemFlags mDragDropFlags; Qt::ItemFlags mDefaultFlags; Qt::DropActions mDropActions; - }; } #endif // CONTENTMODEL_HPP diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index 54199626e0..6965c948ec 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -5,7 +5,6 @@ #include -#include #include #include #include @@ -14,6 +13,9 @@ #include "filewidget.hpp" #include "adjusterwidget.hpp" +#include "textinputdialog.hpp" + +#include ContentSelectorView::ContentSelector *ContentSelectorView::ContentSelector::mInstance = 0; QStringList ContentSelectorView::ContentSelector::mFilePaths; @@ -33,7 +35,8 @@ ContentSelectorView::ContentSelector& ContentSelectorView::ContentSelector::inst ContentSelectorView::ContentSelector::ContentSelector(QWidget *parent, unsigned char flags) : QWidget(parent), mFlags (flags), - mAdjusterWidget (0), mFileWidget (0) + mAdjusterWidget (0), mFileWidget (0), + mIgnoreProfileSignal (false) { ui.setupUi (this); @@ -53,14 +56,6 @@ ContentSelectorView::ContentSelector::ContentSelector(QWidget *parent, unsigned */ } -QString ContentSelectorView::ContentSelector::getNewProfileName() -{ - // Create a dialog for the new profile name input - //mNewProfileDialog = new TextInputDialog(tr("New Profile"), tr("Profile name:"), this); - - //connect(mNewProfileDialog->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(updateOkButton(QString))); - return ""; -} bool ContentSelectorView::ContentSelector::isFlagged(SelectorFlags flag) const { @@ -94,6 +89,8 @@ void ContentSelectorView::ContentSelector::buildGameFileView() return; } + ui.gameFileView->setVisible (true); + mGameFileProxyModel = new QSortFilterProxyModel(this); mGameFileProxyModel->setFilterRegExp(QString::number((int)ContentSelectorModel::ContentType_GameFile)); mGameFileProxyModel->setFilterRole (Qt::UserRole); @@ -102,7 +99,7 @@ void ContentSelectorView::ContentSelector::buildGameFileView() ui.gameFileView->setPlaceholderText(QString("Select a game file...")); ui.gameFileView->setModel(mGameFileProxyModel); - connect(ui.gameFileView, SIGNAL(currentIndexChanged(int)), this, SLOT (slotCurrentGameFileIndexChanged(int))); + connect (ui.gameFileView, SIGNAL(currentIndexChanged(int)), this, SLOT (slotCurrentGameFileIndexChanged(int))); ui.gameFileView->setCurrentIndex(-1); } @@ -115,6 +112,8 @@ void ContentSelectorView::ContentSelector::buildAddonView() return; } + ui.addonView->setVisible (true); + mAddonProxyModel = new QSortFilterProxyModel(this); mAddonProxyModel->setFilterRegExp (QString::number((int)ContentSelectorModel::ContentType_Addon)); mAddonProxyModel->setFilterRole (Qt::UserRole); @@ -129,31 +128,48 @@ void ContentSelectorView::ContentSelector::buildAddonView() void ContentSelectorView::ContentSelector::buildProfilesView() { if (!isFlagged (Flag_Profile)) + { + ui.profileGroupBox->setVisible(false); return; + } + + ui.profileGroupBox->setVisible (true); // Add the actions to the toolbuttons ui.newProfileButton->setDefaultAction (ui.newProfileAction); ui.deleteProfileButton->setDefaultAction (ui.deleteProfileAction); + //enable ui elements ui.profilesComboBox->addItem ("Default"); ui.profilesComboBox->setPlaceholderText (QString("Select a profile...")); + if (!ui.profilesComboBox->isEnabled()) + ui.profilesComboBox->setEnabled(true); + + if (!ui.deleteProfileAction->isEnabled()) + ui.deleteProfileAction->setEnabled(true); + + //establish connections connect (ui.profilesComboBox, SIGNAL (currentIndexChanged(int)), this, SLOT(slotCurrentProfileIndexChanged(int))); connect (ui.profilesComboBox, SIGNAL (profileRenamed(QString,QString)), this, SIGNAL(signalProfileRenamed(QString,QString))); - connect (ui.profilesComboBox, SIGNAL (profileChanged(QString,QString)), this, SIGNAL(signalProfileChanged(QString,QString))); + connect (ui.profilesComboBox, SIGNAL (signalProfileChanged(QString,QString)), this, SIGNAL(signalProfileChangedByUser(QString,QString))); connect (ui.profilesComboBox, SIGNAL (signalProfileTextChanged(QString)), this, SLOT (slotProfileTextChanged (QString))); ui.profileGroupBox->setVisible (true); + ui.projectButtonBox->setVisible (false); } void ContentSelectorView::ContentSelector::buildLoadAddonView() { if (!isFlagged (Flag_LoadAddon)) { - ui.projectGroupBox->setVisible (false); + if (!isFlagged (Flag_NewAddon)) + ui.projectGroupBox->setVisible (false); + return; } + ui.projectGroupBox->setVisible (true); ui.projectCreateButton->setVisible (false); ui.projectGroupBox->setTitle (""); @@ -165,10 +181,14 @@ void ContentSelectorView::ContentSelector::buildNewAddonView() { if (!isFlagged (Flag_NewAddon)) { - ui.profileGroupBox->setVisible (false); + if (!isFlagged (Flag_LoadAddon)) + ui.projectGroupBox->setVisible (false); + return; } + ui.projectGroupBox->setVisible (true); + mFileWidget = new CSVDoc::FileWidget (this); mAdjusterWidget = new CSVDoc::AdjusterWidget (this); @@ -190,18 +210,36 @@ void ContentSelectorView::ContentSelector::buildNewAddonView() connect(ui.projectButtonBox, SIGNAL(rejected()), this, SIGNAL(rejected())); } +void ContentSelectorView::ContentSelector::setGameFile(const QString &filename) +{ + int index = -1; + + if (!filename.isEmpty()) + { + index = ui.gameFileView->findText(filename); + + //verify that the current index is also checked in the model + mContentModel->setCheckState(filename, true); + } + + ui.gameFileView->setCurrentIndex(index); +} + +void ContentSelectorView::ContentSelector::clearCheckStates() +{ + mContentModel->uncheckAll(); +} + void ContentSelectorView::ContentSelector::setCheckStates(const QStringList &list) { if (list.isEmpty()) return; - mContentModel->uncheckAll(); - foreach (const QString &file, list) mContentModel->setCheckState(file, Qt::Checked); } -QString ContentSelectorView::ContentSelector::filename() const +QString ContentSelectorView::ContentSelector::projectFilename() const { QString filepath = ""; @@ -211,26 +249,13 @@ QString ContentSelectorView::ContentSelector::filename() const return filepath; } -QStringList ContentSelectorView::ContentSelector::selectedFilePaths() const -{ - QStringList filePaths; - - if (mContentModel) - { - foreach (ContentSelectorModel::EsmFile *file, mContentModel->checkedItems()) - filePaths.append(file->path()); - } - - return filePaths; -} - ContentSelectorModel::ContentFileList ContentSelectorView::ContentSelector::selectedFiles() const { - if (mContentModel) - return mContentModel->checkedItems(); + if (!mContentModel) + return ContentSelectorModel::ContentFileList(); - return ContentSelectorModel::ContentFileList(); + return mContentModel->checkedItems(); } @@ -243,41 +268,39 @@ void ContentSelectorView::ContentSelector::addFiles(const QString &path) { mInstance->mContentModel->addFiles(path); mInstance->ui.gameFileView->setCurrentIndex(-1); - mInstance->mContentModel->uncheckAll(); } } -void ContentSelectorView::ContentSelector::removeProfile(const QString &item) -{ - int idx = ui.profilesComboBox->findText(item); - - if (idx != -1) - ui.profilesComboBox->removeItem(idx); -} - int ContentSelectorView::ContentSelector::getProfileIndex ( const QString &item) const { return ui.profilesComboBox->findText (item); } -void ContentSelectorView::ContentSelector::setProfileIndex(int index) -{ - if (index >=0 && index < ui.profilesComboBox->count()) - ui.profilesComboBox->setCurrentIndex(index); -} - void ContentSelectorView::ContentSelector::addProfile (const QString &item, bool setAsCurrent) { if (item.isEmpty()) return; + QString previous = ui.profilesComboBox->currentText(); + if (ui.profilesComboBox->findText(item) == -1) ui.profilesComboBox->addItem(item); if (setAsCurrent) - ui.profilesComboBox->setCurrentIndex(ui.profilesComboBox->findText(item)); + setProfile (ui.profilesComboBox->findText(item)); +} - enableProfilesComboBox(); +void ContentSelectorView::ContentSelector::setProfile(int index) +{ + //programmatic change requires second call to non-signalized "slot" since no signal responses + //occur for programmatic changes to the profilesComboBox. + if (index >= -1 && index < ui.profilesComboBox->count()) + { + QString previous = ui.profilesComboBox->itemText(ui.profilesComboBox->currentIndex()); + QString current = ui.profilesComboBox->itemText(index); + + ui.profilesComboBox->setCurrentIndex(index); + } } QString ContentSelectorView::ContentSelector::getProfileText() const @@ -285,36 +308,18 @@ QString ContentSelectorView::ContentSelector::getProfileText() const return ui.profilesComboBox->currentText(); } -void ContentSelectorView::ContentSelector::enableProfilesComboBox() +QAbstractItemModel *ContentSelectorView::ContentSelector::profilesModel() const { - if (!ui.profilesComboBox->isEnabled()) - ui.profilesComboBox->setEnabled(true); - - if (!ui.deleteProfileAction->isEnabled()) - ui.deleteProfileAction->setEnabled(true); - - ui.projectButtonBox->button(QDialogButtonBox::Open)->setEnabled (true); -} - -QStringList ContentSelectorView::ContentSelector::checkedItemsPaths() -{ - QStringList itemPaths; - - foreach( const ContentSelectorModel::EsmFile *file, mContentModel->checkedItems()) - itemPaths << file->path(); - - return itemPaths; + return ui.profilesComboBox->model(); } void ContentSelectorView::ContentSelector::slotCurrentProfileIndexChanged(int index) { //don't allow deleting "Default" profile - bool success = (ui.profilesComboBox->itemText(index) == "Default"); + bool success = (ui.profilesComboBox->itemText(index) != "Default"); ui.deleteProfileAction->setEnabled(success); ui.profilesComboBox->setEditEnabled(success); - - emit signalProfileChanged(index); } void ContentSelectorView::ContentSelector::slotCurrentGameFileIndexChanged(int index) @@ -370,10 +375,12 @@ void ContentSelectorView::ContentSelector::slotUpdateCreateButton(bool) ui.projectCreateButton->setEnabled(validGameFile && validFilename); } - void ContentSelectorView::ContentSelector::on_newProfileAction_triggered() { - emit signalProfileAdded(); + TextInputDialog newDialog (tr("New Profile"), tr("Profile name:"), this); + + if (newDialog.exec() == QDialog::Accepted) + emit signalAddNewProfile(newDialog.getText()); } void ContentSelectorView::ContentSelector::on_deleteProfileAction_triggered() @@ -402,11 +409,6 @@ void ContentSelectorView::ContentSelector::on_deleteProfileAction_triggered() //signal for removal from model emit signalProfileDeleted (profile); -} -/* -void ContentSelectorView::ContentSelector::slotUpdateOkButton(const QString &text) -{ - bool success = (ui.profilesComboBox->findText(text) == -1); - mNewDialog->setOkButtonEnabled(success); -}*/ + slotCurrentProfileIndexChanged(ui.profilesComboBox->currentIndex()); +} diff --git a/components/contentselector/view/contentselector.hpp b/components/contentselector/view/contentselector.hpp index caf9cc670e..64b9b37320 100644 --- a/components/contentselector/view/contentselector.hpp +++ b/components/contentselector/view/contentselector.hpp @@ -7,7 +7,6 @@ #include "../model/contentmodel.hpp" class QSortFilterProxyModel; -class TextInputDialog; namespace CSVDoc { @@ -29,6 +28,7 @@ namespace ContentSelectorView Q_OBJECT unsigned char mFlags; + bool mIgnoreProfileSignal; static ContentSelector *mInstance; static QStringList mFilePaths; @@ -36,8 +36,6 @@ namespace ContentSelectorView CSVDoc::FileWidget *mFileWidget; CSVDoc::AdjusterWidget *mAdjusterWidget; - TextInputDialog *mNewDialog; - protected: ContentSelectorModel::ContentModel *mContentModel; @@ -45,26 +43,28 @@ namespace ContentSelectorView QSortFilterProxyModel *mAddonProxyModel; public: + explicit ContentSelector(QWidget *parent = 0, unsigned char flags = Flag_Content); static void configure(QWidget *subject, unsigned char flags = Flag_Content); static ContentSelector &instance(); static void addFiles(const QString &path); + void clearCheckStates(); void setCheckStates (const QStringList &list); - QStringList checkedItemsPaths(); ContentSelectorModel::ContentFileList *CheckedItems(); - QString filename() const; + QString projectFilename() const; ContentSelectorModel::ContentFileList selectedFiles() const; - QStringList selectedFilePaths() const; + QAbstractItemModel *profilesModel() const; + void setGameFile (const QString &filename = ""); void addProfile (const QString &item, bool setAsCurrent = false); - void removeProfile (const QString &item); + void setProfile (int index); int getProfileIndex (const QString &item) const; - void setProfileIndex (int index); QString getProfileText() const; + private: Ui::DataFilesPage ui; @@ -77,22 +77,18 @@ namespace ContentSelectorView void buildLoadAddonView(); bool isFlagged(SelectorFlags flag) const; - QString getNewProfileName(); - void enableProfilesComboBox(); signals: + void accepted(); void rejected(); - void signalProfileChanged(int index); - void signalGameFileChanged(int value); - void signalCreateButtonClicked(); void signalProfileRenamed(QString,QString); - void signalProfileChanged(QString,QString); + void signalProfileChangedByUser(QString,QString); void signalProfileDeleted(QString); - void signalProfileAdded(); + void signalAddNewProfile(QString); private slots: @@ -102,7 +98,6 @@ namespace ContentSelectorView void slotAddonTableItemClicked(const QModelIndex &index); void slotUpdateCreateButton (bool); - // void slotUpdateOpenButton(const QStringList &items); // Action slots void on_newProfileAction_triggered(); diff --git a/components/contentselector/view/profilescombobox.cpp b/components/contentselector/view/profilescombobox.cpp index 29001189d0..0e9905df45 100644 --- a/components/contentselector/view/profilescombobox.cpp +++ b/components/contentselector/view/profilescombobox.cpp @@ -15,8 +15,8 @@ ContentSelectorView::ProfilesComboBox::ProfilesComboBox(QWidget *parent) : setValidator(mValidator); setCompleter(0); - connect(this, SIGNAL(currentIndexChanged(int)), this, - SLOT(slotIndexChanged(int))); + connect(this, SIGNAL(activated(int)), this, + SLOT(slotIndexChangedByUser(int))); setInsertPolicy(QComboBox::NoInsert); } @@ -85,13 +85,13 @@ void ContentSelectorView::ProfilesComboBox::slotEditingFinished() emit(profileRenamed(previous, current)); } -void ContentSelectorView::ProfilesComboBox::slotIndexChanged(int index) +void ContentSelectorView::ProfilesComboBox::slotIndexChangedByUser(int index) { if (index == -1) return; - emit(profileChanged(mOldProfile, currentText())); - mOldProfile = itemText(index); + emit (signalProfileChanged(mOldProfile, currentText())); + mOldProfile = currentText(); } void ContentSelectorView::ProfilesComboBox::paintEvent(QPaintEvent *) diff --git a/components/contentselector/view/profilescombobox.hpp b/components/contentselector/view/profilescombobox.hpp index 560c42c10f..fc87a94b40 100644 --- a/components/contentselector/view/profilescombobox.hpp +++ b/components/contentselector/view/profilescombobox.hpp @@ -14,17 +14,19 @@ namespace ContentSelectorView public: explicit ProfilesComboBox(QWidget *parent = 0); void setEditEnabled(bool editable); - void setPlaceholderText (const QString &text); + void setPlaceholderText(const QString &text); + // void indexChanged(int index); signals: - void signalProfileTextChanged (const QString &item); - void profileChanged(const QString &previous, const QString ¤t); + void signalProfileTextChanged(const QString &item); + void signalProfileChanged(const QString &previous, const QString ¤t); + void signalProfileChanged(int index); void profileRenamed(const QString &oldName, const QString &newName); private slots: void slotEditingFinished(); - void slotIndexChanged(int index); + void slotIndexChangedByUser(int index); void slotTextChanged(const QString &text); private: diff --git a/apps/launcher/utils/textinputdialog.cpp b/components/contentselector/view/textinputdialog.cpp similarity index 75% rename from apps/launcher/utils/textinputdialog.cpp rename to components/contentselector/view/textinputdialog.cpp index 51928c09a7..6bb92f113e 100644 --- a/apps/launcher/utils/textinputdialog.cpp +++ b/components/contentselector/view/textinputdialog.cpp @@ -16,6 +16,7 @@ TextInputDialog::TextInputDialog(const QString& title, const QString &text, QWid mButtonBox = new QDialogButtonBox(this); mButtonBox->addButton(QDialogButtonBox::Ok); mButtonBox->addButton(QDialogButtonBox::Cancel); + mButtonBox->button(QDialogButtonBox::Ok)->setEnabled (false); // Line edit QValidator *validator = new QRegExpValidator(QRegExp("^[a-zA-Z0-9_]*$"), this); // Alpha-numeric + underscore @@ -38,11 +39,11 @@ TextInputDialog::TextInputDialog(const QString& title, const QString &text, QWid Q_UNUSED(title); #endif - setOkButtonEnabled(false); setModal(true); connect(mButtonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(mButtonBox, SIGNAL(rejected()), this, SLOT(reject())); + connect(mLineEdit, SIGNAL(textChanged(QString)), this, SLOT(slotUpdateOkButton(QString))); } @@ -53,19 +54,23 @@ int TextInputDialog::exec() return QDialog::exec(); } -void TextInputDialog::setOkButtonEnabled(bool enabled) +QString TextInputDialog::getText() const { - QPushButton *okButton = mButtonBox->button(QDialogButtonBox::Ok); - okButton->setEnabled(enabled); + return mLineEdit->text(); +} - QPalette *palette = new QPalette(); - palette->setColor(QPalette::Text,Qt::red); +void TextInputDialog::slotUpdateOkButton(QString text) +{ + bool enabled = !(text.isEmpty()); + mButtonBox->button(QDialogButtonBox::Ok)->setEnabled(enabled); - if (enabled) { + if (enabled) mLineEdit->setPalette(QApplication::palette()); - } else { + else + { // Existing profile name, make the text red + QPalette *palette = new QPalette(); + palette->setColor(QPalette::Text,Qt::red); mLineEdit->setPalette(*palette); } - } diff --git a/apps/launcher/utils/textinputdialog.hpp b/components/contentselector/view/textinputdialog.hpp similarity index 78% rename from apps/launcher/utils/textinputdialog.hpp rename to components/contentselector/view/textinputdialog.hpp index de3a9fb723..a0b7e350a5 100644 --- a/apps/launcher/utils/textinputdialog.hpp +++ b/components/contentselector/view/textinputdialog.hpp @@ -13,18 +13,19 @@ namespace ContentSelectorView { class TextInputDialog : public QDialog { Q_OBJECT -public: - explicit TextInputDialog(const QString& title, const QString &text, QWidget *parent = 0); - inline ContentSelectorView::LineEdit *lineEdit() { return mLineEdit; } - void setOkButtonEnabled(bool enabled); ContentSelectorView::LineEdit *mLineEdit; + QDialogButtonBox *mButtonBox; + +public: + + explicit TextInputDialog(const QString& title, const QString &text, QWidget *parent = 0); + QString getText() const; int exec(); -private: - QDialogButtonBox *mButtonBox; - +private slots: + void slotUpdateOkButton(QString text); }; diff --git a/files/ui/datafilespage.ui b/files/ui/datafilespage.ui index 73d7a4902e..0cafd606a8 100644 --- a/files/ui/datafilespage.ui +++ b/files/ui/datafilespage.ui @@ -7,7 +7,7 @@ 0 0 518 - 424 + 436 @@ -242,11 +242,6 @@ 0 - - - Default - -
From 4c72a9ffdfe592f154beeb4be2d05d3c1514fc01 Mon Sep 17 00:00:00 2001 From: graffy76 Date: Sun, 6 Oct 2013 22:10:38 -0500 Subject: [PATCH 074/434] Fixed non-loading files --- apps/opencs/editor.cpp | 3 +++ apps/opencs/view/doc/filedialog.cpp | 2 +- components/contentselector/model/contentmodel.cpp | 11 ++++++----- components/contentselector/model/contentmodel.hpp | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 401f3839f1..ae10ec6422 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -128,6 +128,9 @@ void CS::Editor::openFiles() files.push_back(path.toStdString()); } + foreach (const boost::filesystem::path fp, files) + qDebug() << "loading files: " << fp.c_str(); + /// \todo Get the save path from the file dialogue CSMDoc::Document *document = mDocumentManager.addDocument (files, *files.rbegin(), false); diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index efa31100ab..2017ab1036 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -44,7 +44,7 @@ QStringList CSVDoc::FileDialog::selectedFilePaths() foreach (ContentSelectorModel::EsmFile *file, ContentSelectorView::ContentSelector:: instance().selectedFiles() ) { - filePaths.append(file->fileName()); + filePaths.append(file->path()); } return filePaths; } diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index a9796a1fbf..8bb052d3dc 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -15,10 +15,10 @@ ContentSelectorModel::ContentModel::ContentModel(QObject *parent) : mDefaultFlags (Qt::ItemIsDropEnabled | Qt::ItemIsSelectable), mDropActions (Qt::CopyAction | Qt::MoveAction) { - // setEncoding ("win1252"); + setEncoding ("win1252"); uncheckAll(); } -/* + void ContentSelectorModel::ContentModel::setEncoding(const QString &encoding) { if (encoding == QLatin1String("win1252")) @@ -33,7 +33,7 @@ void ContentSelectorModel::ContentModel::setEncoding(const QString &encoding) else return; // This should never happen; } -*/ + int ContentSelectorModel::ContentModel::columnCount(const QModelIndex &parent) const { if (parent.isValid()) @@ -420,8 +420,9 @@ void ContentSelectorModel::ContentModel::addFiles(const QString &path) try { ESM::ESMReader fileReader; - ToUTF8::Utf8Encoder encoder(); //ToUTF8::calculateEncoding(QString(mCodec->name()).toStdString())); - //fileReader.setEncoder(&encoder); + ToUTF8::Utf8Encoder encoder = + ToUTF8::calculateEncoding(QString(mCodec->name()).toStdString()); + fileReader.setEncoder(&encoder); fileReader.open(dir.absoluteFilePath(path).toStdString()); foreach (const ESM::Header::MasterData &item, fileReader.getGameFiles()) diff --git a/components/contentselector/model/contentmodel.hpp b/components/contentselector/model/contentmodel.hpp index feea3643b3..0d6c52cc63 100644 --- a/components/contentselector/model/contentmodel.hpp +++ b/components/contentselector/model/contentmodel.hpp @@ -22,7 +22,7 @@ namespace ContentSelectorModel public: explicit ContentModel(QObject *parent = 0); - //void setEncoding(const QString &encoding); + void setEncoding(const QString &encoding); int rowCount(const QModelIndex &parent = QModelIndex()) const; int columnCount(const QModelIndex &parent = QModelIndex()) const; From 1ac3d99c78adc9bbef46256ba33c0a109e2a41b0 Mon Sep 17 00:00:00 2001 From: gus Date: Mon, 7 Oct 2013 10:20:02 +0200 Subject: [PATCH 075/434] pathfinding now works in AICombat. --- apps/openmw/mwmechanics/aicombat.cpp | 20 ++++++++++++++------ apps/openmw/mwmechanics/aicombat.hpp | 1 + apps/openmw/mwmechanics/pathfinding.cpp | 17 ++++++++++++++++- apps/openmw/mwmechanics/pathfinding.hpp | 12 ++++++++++++ 4 files changed, 43 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index bf5968e8c7..529855b2ff 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -55,14 +55,20 @@ namespace MWMechanics start.mY = pos.pos[1]; start.mZ = pos.pos[2]; - std::cout << start.mX << " " << dest.mX << "\n"; - - mPathFinder.buildPath(start, dest, pathgrid, xCell, yCell, true); - + if(!mPathFinder.isPathConstructed()) + mPathFinder.buildPath(start, dest, pathgrid, xCell, yCell, true); + else + { + mPathFinder2.buildPath(start, dest, pathgrid, xCell, yCell, true); + ESM::Pathgrid::Point lastPt = mPathFinder.getPath().back(); + if(mPathFinder2.getPathSize() < mPathFinder.getPathSize() || + (dest.mX - lastPt.mX)*(dest.mX - lastPt.mX)+(dest.mY - lastPt.mY)*(dest.mY - lastPt.mY)+(dest.mZ - lastPt.mZ)*(dest.mZ - lastPt.mZ) > 200*200) + mPathFinder = mPathFinder2; + } + mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2]); float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); - std::cout << zAngle; MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false); MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; @@ -71,7 +77,9 @@ namespace MWMechanics if((dest.mX - start.mX)*(dest.mX - start.mX)+(dest.mY - start.mY)*(dest.mY - start.mY)+(dest.mZ - start.mZ)*(dest.mZ - start.mZ) < range*range) { - MWWorld::TimeStamp time = MWBase::Environment::get().getWorld()->getTimeStamp(); + mPathFinder.clearPath(); + + MWWorld::TimeStamp time = MWBase::Environment::get().getWorld()->getTimeStamp(); if(mStartingSecond == 0) { MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(false); diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index bde66a46bc..f61582adc8 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -33,6 +33,7 @@ namespace MWMechanics std::string mTargetId; PathFinder mPathFinder; + PathFinder mPathFinder2; unsigned int mStartingSecond; }; } diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 8ef0edab83..ff266f9ae7 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -189,7 +189,7 @@ namespace MWMechanics // This should never happen (programmers should have an if statement checking mIsPathConstructed that prevents this call // if otherwise). if(mPath.empty()) - return 0; + return 0.; const ESM::Pathgrid::Point &nextPoint = *mPath.begin(); float directionX = nextPoint.mX - x; @@ -199,6 +199,21 @@ namespace MWMechanics return Ogre::Radian(acos(directionY / directionResult) * sgn(asin(directionX / directionResult))).valueDegrees(); } + bool PathFinder::checkWaypoint(float x, float y, float z) + { + if(mPath.empty()) + return true; + + ESM::Pathgrid::Point nextPoint = *mPath.begin(); + if(distanceZCorrected(nextPoint, x, y, z) < 64) + { + mPath.pop_front(); + if(mPath.empty()) mIsPathConstructed = false; + return true; + } + return false; + } + bool PathFinder::checkPathCompleted(float x, float y, float z) { if(mPath.empty()) diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 35e0fa9087..916df850b5 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -18,6 +18,8 @@ namespace MWMechanics bool checkPathCompleted(float x, float y, float z); ///< \Returns true if the last point of the path has been reached. + bool checkWaypoint(float x, float y, float z); + ///< \Returns true if a way point was reached float getZAngleToNext(float x, float y) const; bool isPathConstructed() const @@ -25,6 +27,16 @@ namespace MWMechanics return mIsPathConstructed; } + int getPathSize() const + { + return mPath.size(); + } + + std::list getPath() const + { + return mPath; + } + private: std::list mPath; bool mIsPathConstructed; From 94069e3a7e35225f9264fc9970e80257e4903011 Mon Sep 17 00:00:00 2001 From: gus Date: Mon, 7 Oct 2013 20:30:12 +0200 Subject: [PATCH 076/434] bugfix --- apps/openmw/mwmechanics/aisequence.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index f3aa877e24..ecd21d04c3 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -23,7 +23,7 @@ void MWMechanics::AiSequence::copy (const AiSequence& sequence) iter!=sequence.mPackages.end(); ++iter) mPackages.push_back ((*iter)->clone()); mCombat = sequence.mCombat; - mCombatPackage = sequence.mCombatPackage; + mCombatPackage = sequence.mCombatPackage->clone(); } MWMechanics::AiSequence::AiSequence() : mDone (false), mCombat (false), mCombatPackage (0) {} @@ -93,7 +93,11 @@ void MWMechanics::AiSequence::clear() for (std::list::const_iterator iter (mPackages.begin()); iter!=mPackages.end(); ++iter) delete *iter; - if(mCombatPackage) delete mCombatPackage; + if(mCombatPackage) + { + delete mCombatPackage; + mCombatPackage = 0; + } mPackages.clear(); } From 3000386443acf0a8214994aaa14e59c68eb6d05f Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Wed, 16 Oct 2013 13:07:26 +0200 Subject: [PATCH 077/434] failed attempt on switch adding. --- apps/opencs/editor.cpp | 54 +++++++++++++++++++++++++++++++++++++++++- apps/opencs/editor.hpp | 5 +++- apps/opencs/main.cpp | 2 +- 3 files changed, 58 insertions(+), 3 deletions(-) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index a430597953..4c75ed1cc9 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -6,6 +6,8 @@ #include #include +#include + #include "model/doc/document.hpp" #include "model/world/data.hpp" @@ -208,8 +210,13 @@ void CS::Editor::connectToIPCServer() mClientSocket->close(); } -int CS::Editor::run() +int CS::Editor::run(int argc, char** argv) { + if (!parseOptions(argc, argv) ) + { + return 0; + } + if (mLocal.empty()) return 1; @@ -219,3 +226,48 @@ int CS::Editor::run() return QApplication::exec(); } + +bool CS::Editor::parseOptions (int argc, char** argv) +{ + // Create a local alias for brevity + namespace bpo = boost::program_options; + typedef std::vector StringsVector; + + bpo::options_description desc("Syntax: openmw \nAllowed options"); + + desc.add_options() + ("help", "print help message") + + ("resources", bpo::value()->default_value("resources"), "set resources directory"); + + bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv).options(desc).allow_unregistered().run(); + + bpo::variables_map variables; + + // Runtime options override settings from all configs + bpo::store(valid_opts, variables); + bpo::notify(variables); + +// cfgMgr.readConfiguration(variables, desc); + + bool run = true; + + if (variables.count ("help")) + { + std::cout << desc << std::endl; + run = false; + } + + if (!run) + return false; + + setResourceDir(variables["resources"].as()); + + return true; +} + +// Set resource dir +void CS::Editor::setResourceDir (const boost::filesystem::path& parResDir) +{ + mResDir = boost::filesystem::system_complete(parResDir); +} \ No newline at end of file diff --git a/apps/opencs/editor.hpp b/apps/opencs/editor.hpp index 16f6b9516c..77ba0993e9 100644 --- a/apps/opencs/editor.hpp +++ b/apps/opencs/editor.hpp @@ -50,7 +50,7 @@ namespace CS bool makeIPCServer(); void connectToIPCServer(); - int run(); + int run(int argc, char** argv); ///< \return error status private slots: @@ -66,12 +66,15 @@ namespace CS void showStartup(); void showSettings(); + bool parseOptions (int argc, char** argv); + void setResourceDir (const boost::filesystem::path& parResDir); private: QString mIpcServerName; QLocalServer *mServer; QLocalSocket *mClientSocket; + boost::filesystem::path mResDir; }; } diff --git a/apps/opencs/main.cpp b/apps/opencs/main.cpp index e5e7514ce0..bec09bd4aa 100644 --- a/apps/opencs/main.cpp +++ b/apps/opencs/main.cpp @@ -45,5 +45,5 @@ int main(int argc, char *argv[]) // return 0; } - return editor.run(); + return editor.run(argc, argv); } From a7002e8a09602f4dfcbe3e1031ab6a4ef365cbad Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Thu, 17 Oct 2013 18:21:41 +0200 Subject: [PATCH 078/434] Implements switch (--help and --resources), and copying defaultfilters.omwaddon.project. Seems to work. --- apps/opencs/editor.cpp | 17 ++++------------- apps/opencs/editor.hpp | 2 -- apps/opencs/model/doc/document.cpp | 10 +++++----- apps/opencs/model/doc/document.hpp | 7 +++---- apps/opencs/model/doc/documentmanager.cpp | 7 ++++++- apps/opencs/model/doc/documentmanager.hpp | 7 +++++-- 6 files changed, 23 insertions(+), 27 deletions(-) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 4c75ed1cc9..d5888a3124 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -6,8 +6,6 @@ #include #include -#include - #include "model/doc/document.hpp" #include "model/world/data.hpp" @@ -59,7 +57,7 @@ void CS::Editor::setupDataFiles() if (!variables["data"].empty()) { dataDirs = Files::PathContainer(variables["data"].as()); } - + std::string local = variables["data-local"].as(); if (!local.empty()) { dataLocal.push_back(Files::PathContainer::value_type(local)); @@ -231,9 +229,8 @@ bool CS::Editor::parseOptions (int argc, char** argv) { // Create a local alias for brevity namespace bpo = boost::program_options; - typedef std::vector StringsVector; - bpo::options_description desc("Syntax: openmw \nAllowed options"); + bpo::options_description desc("Syntax: opencs \nAllowed options"); desc.add_options() ("help", "print help message") @@ -248,7 +245,7 @@ bool CS::Editor::parseOptions (int argc, char** argv) bpo::store(valid_opts, variables); bpo::notify(variables); -// cfgMgr.readConfiguration(variables, desc); + mCfgMgr.readConfiguration(variables, desc); bool run = true; @@ -261,13 +258,7 @@ bool CS::Editor::parseOptions (int argc, char** argv) if (!run) return false; - setResourceDir(variables["resources"].as()); + mDocumentManager.setResourceDir(variables["resources"].as()); return true; -} - -// Set resource dir -void CS::Editor::setResourceDir (const boost::filesystem::path& parResDir) -{ - mResDir = boost::filesystem::system_complete(parResDir); } \ No newline at end of file diff --git a/apps/opencs/editor.hpp b/apps/opencs/editor.hpp index 77ba0993e9..763cde1002 100644 --- a/apps/opencs/editor.hpp +++ b/apps/opencs/editor.hpp @@ -67,14 +67,12 @@ namespace CS void showSettings(); bool parseOptions (int argc, char** argv); - void setResourceDir (const boost::filesystem::path& parResDir); private: QString mIpcServerName; QLocalServer *mServer; QLocalSocket *mClientSocket; - boost::filesystem::path mResDir; }; } diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index 5c29d9f617..afb68b440f 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -2146,10 +2146,8 @@ void CSMDoc::Document::createBase() } } -CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, - const std::vector& files, - const boost::filesystem::path& savePath, bool new_) -: mSavePath (savePath), mContentFiles (files), mTools (mData), +CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, const std::vector< boost::filesystem::path >& files, const boost::filesystem::path& savePath, const boost::filesystem::path& resDir, bool new_) +: mSavePath (savePath), mContentFiles (files), mTools (mData), mResDir(resDir), mProjectPath ((configuration.getUserPath() / "projects") / (savePath.filename().string() + ".project")), mSaving (*this, mProjectPath) @@ -2183,7 +2181,9 @@ CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, } else { - /// \todo create new project file with default filters + boost::filesystem::path filters = mResDir; + filters /= "defaultfilters.omwaddon.project"; + boost::filesystem::copy_file(mResDir, mProjectPath); } } diff --git a/apps/opencs/model/doc/document.hpp b/apps/opencs/model/doc/document.hpp index d171dacaed..e6d1b0c872 100644 --- a/apps/opencs/model/doc/document.hpp +++ b/apps/opencs/model/doc/document.hpp @@ -43,13 +43,14 @@ namespace CSMDoc CSMTools::Tools mTools; boost::filesystem::path mProjectPath; Saving mSaving; + boost::filesystem::path mResDir; // It is important that the undo stack is declared last, because on desctruction it fires a signal, that is connected to a slot, that is // using other member variables. Unfortunately this connection is cut only in the QObject destructor, which is way too late. QUndoStack mUndoStack; // not implemented - Document (const Document&); + Document (const Files::ConfigurationManager& configuration, const std::vector< boost::filesystem::path >& files, const boost::filesystem::path& savePath, bool new_); Document& operator= (const Document&); void load (const std::vector::const_iterator& begin, @@ -70,9 +71,7 @@ namespace CSMDoc public: - Document (const Files::ConfigurationManager& configuration, - const std::vector& files, - const boost::filesystem::path& savePath, bool new_); + Document (const Files::ConfigurationManager& configuration, const std::vector< boost::filesystem::path >& files, const boost::filesystem::path& savePath, const boost::filesystem::path& resDir, bool new_); ~Document(); diff --git a/apps/opencs/model/doc/documentmanager.cpp b/apps/opencs/model/doc/documentmanager.cpp index 1d6c88dccf..024c46beae 100644 --- a/apps/opencs/model/doc/documentmanager.cpp +++ b/apps/opencs/model/doc/documentmanager.cpp @@ -30,7 +30,7 @@ CSMDoc::DocumentManager::~DocumentManager() CSMDoc::Document *CSMDoc::DocumentManager::addDocument (const std::vector& files, const boost::filesystem::path& savePath, bool new_) { - Document *document = new Document (mConfiguration, files, savePath, new_); + Document *document = new Document (mConfiguration, files, savePath, mResDir, new_); mDocuments.push_back (document); @@ -48,4 +48,9 @@ bool CSMDoc::DocumentManager::removeDocument (Document *document) delete document; return mDocuments.empty(); +} + +void CSMDoc::DocumentManager::setResourceDir (const boost::filesystem::path& parResDir) +{ + mResDir = boost::filesystem::system_complete(parResDir); } \ No newline at end of file diff --git a/apps/opencs/model/doc/documentmanager.hpp b/apps/opencs/model/doc/documentmanager.hpp index 28a21216a6..b80a186429 100644 --- a/apps/opencs/model/doc/documentmanager.hpp +++ b/apps/opencs/model/doc/documentmanager.hpp @@ -29,8 +29,7 @@ namespace CSMDoc ~DocumentManager(); - Document *addDocument (const std::vector& files, - const boost::filesystem::path& savePath, bool new_); + Document *addDocument (const std::vector< boost::filesystem::path >& files, const boost::filesystem::path& savePath, bool new_); ///< The ownership of the returned document is not transferred to the caller. /// /// \param new_ Do not load the last content file in \a files and instead create in an @@ -38,6 +37,10 @@ namespace CSMDoc bool removeDocument (Document *document); ///< \return last document removed? + void setResourceDir (const boost::filesystem::path& parResDir); + + private: + boost::filesystem::path mResDir; }; } From 4e26a61db3f217a8bf4bb6ce2f36e8a0a65b57c5 Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Fri, 18 Oct 2013 22:11:14 +0200 Subject: [PATCH 079/434] Removed command line handling. Maybe zini will let me to implement it later. Implemented switch to handle resources directory. TODO: check for defaultfilters on data path. --- apps/opencs/editor.cpp | 55 ++++++------------------------------------ apps/opencs/editor.hpp | 3 +-- apps/opencs/main.cpp | 2 +- 3 files changed, 10 insertions(+), 50 deletions(-) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index d5888a3124..97c958f3da 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -41,13 +41,14 @@ CS::Editor::Editor() void CS::Editor::setupDataFiles() { boost::program_options::variables_map variables; - boost::program_options::options_description desc; - + boost::program_options::options_description desc("Syntax: opencs \nAllowed options"); + desc.add_options() ("data", boost::program_options::value()->default_value(Files::PathContainer(), "data")->multitoken()) ("data-local", boost::program_options::value()->default_value("")) ("fs-strict", boost::program_options::value()->implicit_value(true)->default_value(false)) - ("encoding", boost::program_options::value()->default_value("win1252")); + ("encoding", boost::program_options::value()->default_value("win1252")) + ("resources", boost::program_options::value()->default_value("resources"), "set resources directory"); boost::program_options::notify(variables); @@ -86,6 +87,9 @@ void CS::Editor::setupDataFiles() //mFileDialog.setEncoding(encoding); dataDirs.insert (dataDirs.end(), dataLocal.begin(), dataLocal.end()); + +// Setting Resources directory. + mDocumentManager.setResourceDir(variables["resources"].as()); for (Files::PathContainer::const_iterator iter = dataDirs.begin(); iter != dataDirs.end(); ++iter) { @@ -208,13 +212,8 @@ void CS::Editor::connectToIPCServer() mClientSocket->close(); } -int CS::Editor::run(int argc, char** argv) +int CS::Editor::run() { - if (!parseOptions(argc, argv) ) - { - return 0; - } - if (mLocal.empty()) return 1; @@ -223,42 +222,4 @@ int CS::Editor::run(int argc, char** argv) QApplication::setQuitOnLastWindowClosed (true); return QApplication::exec(); -} - -bool CS::Editor::parseOptions (int argc, char** argv) -{ - // Create a local alias for brevity - namespace bpo = boost::program_options; - - bpo::options_description desc("Syntax: opencs \nAllowed options"); - - desc.add_options() - ("help", "print help message") - - ("resources", bpo::value()->default_value("resources"), "set resources directory"); - - bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv).options(desc).allow_unregistered().run(); - - bpo::variables_map variables; - - // Runtime options override settings from all configs - bpo::store(valid_opts, variables); - bpo::notify(variables); - - mCfgMgr.readConfiguration(variables, desc); - - bool run = true; - - if (variables.count ("help")) - { - std::cout << desc << std::endl; - run = false; - } - - if (!run) - return false; - - mDocumentManager.setResourceDir(variables["resources"].as()); - - return true; } \ No newline at end of file diff --git a/apps/opencs/editor.hpp b/apps/opencs/editor.hpp index 763cde1002..16f6b9516c 100644 --- a/apps/opencs/editor.hpp +++ b/apps/opencs/editor.hpp @@ -50,7 +50,7 @@ namespace CS bool makeIPCServer(); void connectToIPCServer(); - int run(int argc, char** argv); + int run(); ///< \return error status private slots: @@ -66,7 +66,6 @@ namespace CS void showStartup(); void showSettings(); - bool parseOptions (int argc, char** argv); private: diff --git a/apps/opencs/main.cpp b/apps/opencs/main.cpp index bec09bd4aa..e5e7514ce0 100644 --- a/apps/opencs/main.cpp +++ b/apps/opencs/main.cpp @@ -45,5 +45,5 @@ int main(int argc, char *argv[]) // return 0; } - return editor.run(argc, argv); + return editor.run(); } From 184456892b4abc42b4db73f66ecb82c21073011e Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Sat, 19 Oct 2013 18:43:47 +0200 Subject: [PATCH 080/434] Added check to load custom filters set when present. --- apps/opencs/editor.cpp | 21 +++++++++++---------- apps/opencs/model/doc/document.cpp | 26 +++++++++++++++++--------- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 97c958f3da..9abc9ee780 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -8,10 +8,11 @@ #include "model/doc/document.hpp" #include "model/world/data.hpp" +#include CS::Editor::Editor() -: mDocumentManager (mCfgMgr), mViewManager (mDocumentManager) + : mDocumentManager (mCfgMgr), mViewManager (mDocumentManager) { mIpcServerName = "org.openmw.OpenCS"; @@ -32,23 +33,23 @@ CS::Editor::Editor() connect (&mFileDialog, SIGNAL(openFiles()), this, SLOT(openFiles())); connect (&mFileDialog, SIGNAL(createNewFile (const boost::filesystem::path&)), - this, SLOT(createNewFile (const boost::filesystem::path&))); + this, SLOT(createNewFile (const boost::filesystem::path&))); connect (&mNewGame, SIGNAL (createRequest (const boost::filesystem::path&)), - this, SLOT (createNewGame (const boost::filesystem::path&))); + this, SLOT (createNewGame (const boost::filesystem::path&))); } void CS::Editor::setupDataFiles() { boost::program_options::variables_map variables; boost::program_options::options_description desc("Syntax: opencs \nAllowed options"); - + desc.add_options() ("data", boost::program_options::value()->default_value(Files::PathContainer(), "data")->multitoken()) ("data-local", boost::program_options::value()->default_value("")) ("fs-strict", boost::program_options::value()->implicit_value(true)->default_value(false)) ("encoding", boost::program_options::value()->default_value("win1252")) - ("resources", boost::program_options::value()->default_value("resources"), "set resources directory"); + ("resources", boost::program_options::value()->default_value("resources")); boost::program_options::notify(variables); @@ -58,7 +59,7 @@ void CS::Editor::setupDataFiles() if (!variables["data"].empty()) { dataDirs = Files::PathContainer(variables["data"].as()); } - + std::string local = variables["data-local"].as(); if (!local.empty()) { dataLocal.push_back(Files::PathContainer::value_type(local)); @@ -83,12 +84,12 @@ void CS::Editor::setupDataFiles() } // Set the charset for reading the esm/esp files - // QString encoding = QString::fromStdString(variables["encoding"].as()); + // QString encoding = QString::fromStdString(variables["encoding"].as()); //mFileDialog.setEncoding(encoding); dataDirs.insert (dataDirs.end(), dataLocal.begin(), dataLocal.end()); - -// Setting Resources directory. + +// Adding Resources directory. First check if there is a file defaultfilters in the user path. mDocumentManager.setResourceDir(variables["resources"].as()); for (Files::PathContainer::const_iterator iter = dataDirs.begin(); iter != dataDirs.end(); ++iter) @@ -222,4 +223,4 @@ int CS::Editor::run() QApplication::setQuitOnLastWindowClosed (true); return QApplication::exec(); -} \ No newline at end of file +} diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index afb68b440f..32c728f88c 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -9,7 +9,7 @@ #endif void CSMDoc::Document::load (const std::vector::const_iterator& begin, - const std::vector::const_iterator& end, bool lastAsModified) + const std::vector::const_iterator& end, bool lastAsModified) { assert (begin!=end); @@ -2147,10 +2147,10 @@ void CSMDoc::Document::createBase() } CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, const std::vector< boost::filesystem::path >& files, const boost::filesystem::path& savePath, const boost::filesystem::path& resDir, bool new_) -: mSavePath (savePath), mContentFiles (files), mTools (mData), mResDir(resDir), - mProjectPath ((configuration.getUserPath() / "projects") / - (savePath.filename().string() + ".project")), - mSaving (*this, mProjectPath) + : mSavePath (savePath), mContentFiles (files), mTools (mData), mResDir(resDir), + mProjectPath ((configuration.getUserPath() / "projects") / + (savePath.filename().string() + ".project")), + mSaving (*this, mProjectPath) { if (files.empty()) throw std::runtime_error ("Empty content file sequence"); @@ -2181,9 +2181,17 @@ CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, co } else { - boost::filesystem::path filters = mResDir; - filters /= "defaultfilters.omwaddon.project"; - boost::filesystem::copy_file(mResDir, mProjectPath); + boost::filesystem::path locCustomFiltersPath (configuration.getUserPath()); + locCustomFiltersPath /= "defaultfilters.omwaddon.project"; + if (boost::filesystem::exists(locCustomFiltersPath)) + { + boost::filesystem::copy(locCustomFiltersPath, mProjectPath); + } else { + boost::filesystem::path filters(mResDir); + filters /= "defaultfilters.omwaddon.project"; + boost::filesystem::copy_file(filters, mProjectPath); + } + getData().loadFile (mProjectPath, false, true); } } @@ -2198,7 +2206,7 @@ CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, co connect (&mSaving, SIGNAL (progress (int, int, int)), this, SLOT (progress (int, int, int))); connect (&mSaving, SIGNAL (done (int)), this, SLOT (operationDone (int))); connect (&mSaving, SIGNAL (reportMessage (const QString&, int)), - this, SLOT (reportMessage (const QString&, int))); + this, SLOT (reportMessage (const QString&, int))); } CSMDoc::Document::~Document() From 762cbf62785d8f26ea2d58bcf24a65f32bedf64d Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Sat, 19 Oct 2013 18:51:36 +0200 Subject: [PATCH 081/434] Changed gauntlets filter. It showes now both gauntlets and bracers. --- files/opencs/defaultfilters.omwaddon.project | Bin 0 -> 10544 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 files/opencs/defaultfilters.omwaddon.project diff --git a/files/opencs/defaultfilters.omwaddon.project b/files/opencs/defaultfilters.omwaddon.project new file mode 100644 index 0000000000000000000000000000000000000000..e58ed92786f2e0333519341d24f008e772828e34 GIT binary patch literal 10544 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 AoB#j- literal 0 HcmV?d00001 From cafea438bdc1108c0a6cdf1f183b453ee2ef2bf9 Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Sun, 20 Oct 2013 09:44:10 +0200 Subject: [PATCH 082/434] Added missing light filter. --- files/opencs/defaultfilters.omwaddon.project | Bin 10544 -> 10962 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/files/opencs/defaultfilters.omwaddon.project b/files/opencs/defaultfilters.omwaddon.project index e58ed92786f2e0333519341d24f008e772828e34..c66990fdc2c26863fa33edefe0d336ef9ae8fa3b 100644 GIT binary patch delta 295 zcmdlGbSZR$f#&43jEX!tnduoN#SCtqJ|VJ`8+j#FeLw=53@)z0&VmdK48a-s(uCL|53?mdFQ-x=FF#KKW+aN8{gW9bWh5YW7MCQJWF~`cbVAtZ53?~Z Mzl3yKbunxO07-RSssI20 delta 7 OcmcZ Date: Sun, 20 Oct 2013 09:50:16 +0200 Subject: [PATCH 083/434] Added configrue file in cmake. Hopefully it will copy defaultfilters file. --- CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 625239aa06..a436d1fc3c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -319,6 +319,9 @@ configure_file(${OpenMW_SOURCE_DIR}/files/openmw.cfg configure_file(${OpenMW_SOURCE_DIR}/files/opencs.cfg "${OpenMW_BINARY_DIR}/opencs.cfg") + +configure_file(${OpenMW_SOURCE_DIR}/files/opencs/defaultfilters.omwaddon.project + "${OpenMW_BINARY_DIR}/resources/defaultfilters.omwaddon.project") if (NOT WIN32 AND NOT APPLE) configure_file(${OpenMW_SOURCE_DIR}/files/openmw.desktop From 6b293961b4c7ee5152a282a6c245ec57c227b128 Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Sun, 20 Oct 2013 10:02:33 +0200 Subject: [PATCH 084/434] This appears to work. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a436d1fc3c..2a902eb832 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -321,7 +321,7 @@ configure_file(${OpenMW_SOURCE_DIR}/files/opencs.cfg "${OpenMW_BINARY_DIR}/opencs.cfg") configure_file(${OpenMW_SOURCE_DIR}/files/opencs/defaultfilters.omwaddon.project - "${OpenMW_BINARY_DIR}/resources/defaultfilters.omwaddon.project") + "${OpenMW_BINARY_DIR}/resources/defaultfilters.omwaddon.project" COPYONLY) if (NOT WIN32 AND NOT APPLE) configure_file(${OpenMW_SOURCE_DIR}/files/openmw.desktop From 96b6787255fb4596a0b02d3cecc438c086e29c73 Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Sun, 20 Oct 2013 10:56:27 +0200 Subject: [PATCH 085/434] Getting rid of extension. Correcting tiny mistake in filters file. --- CMakeLists.txt | 2 +- apps/opencs/model/doc/document.cpp | 4 ++-- files/opencs/defaultfilters.omwaddon.project | Bin 10962 -> 10958 bytes 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2a902eb832..df2986715b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -321,7 +321,7 @@ configure_file(${OpenMW_SOURCE_DIR}/files/opencs.cfg "${OpenMW_BINARY_DIR}/opencs.cfg") configure_file(${OpenMW_SOURCE_DIR}/files/opencs/defaultfilters.omwaddon.project - "${OpenMW_BINARY_DIR}/resources/defaultfilters.omwaddon.project" COPYONLY) + "${OpenMW_BINARY_DIR}/resources/defaultfilters" COPYONLY) if (NOT WIN32 AND NOT APPLE) configure_file(${OpenMW_SOURCE_DIR}/files/openmw.desktop diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index 32c728f88c..fd2b328612 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -2182,13 +2182,13 @@ CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, co else { boost::filesystem::path locCustomFiltersPath (configuration.getUserPath()); - locCustomFiltersPath /= "defaultfilters.omwaddon.project"; + locCustomFiltersPath /= "customfilters.omwaddon.project"; if (boost::filesystem::exists(locCustomFiltersPath)) { boost::filesystem::copy(locCustomFiltersPath, mProjectPath); } else { boost::filesystem::path filters(mResDir); - filters /= "defaultfilters.omwaddon.project"; + filters /= "defaultfilters"; boost::filesystem::copy_file(filters, mProjectPath); } getData().loadFile (mProjectPath, false, true); diff --git a/files/opencs/defaultfilters.omwaddon.project b/files/opencs/defaultfilters.omwaddon.project index c66990fdc2c26863fa33edefe0d336ef9ae8fa3b..0ac3c8db4bd939b8b9d559ab4af4056fc0b8d80e 100644 GIT binary patch delta 37 tcmcZXdMR|nEKSDV$rIINCU4h_oV;2~aI&n{MgWvH3W5Lt From ebf77329128ac86833586ce4e00ceec2916d3e1e Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 20 Oct 2013 15:48:39 +0200 Subject: [PATCH 086/434] some cleanup --- apps/opencs/model/world/idcollection.hpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/apps/opencs/model/world/idcollection.hpp b/apps/opencs/model/world/idcollection.hpp index 04e65eea7b..72aafb91fe 100644 --- a/apps/opencs/model/world/idcollection.hpp +++ b/apps/opencs/model/world/idcollection.hpp @@ -14,14 +14,11 @@ namespace CSMWorld { public: - void load (ESM::ESMReader& reader, bool base, - UniversalId::Type type = UniversalId::Type_None); - ///< \param type Will be ignored, unless the collection supports multiple record types + void load (ESM::ESMReader& reader, bool base); }; template - void IdCollection::load (ESM::ESMReader& reader, bool base, - UniversalId::Type type) + void IdCollection::load (ESM::ESMReader& reader, bool base) { std::string id = reader.getHNOString ("NAME"); From adf3a41a835af0315adf53ab7a31f3a1c2356cfc Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 20 Oct 2013 17:13:31 +0200 Subject: [PATCH 087/434] added topic and journal tables --- apps/opencs/model/world/data.cpp | 58 ++++++++++++++++++++++++ apps/opencs/model/world/data.hpp | 11 +++++ apps/opencs/model/world/idcollection.hpp | 46 +++++++++++-------- apps/opencs/model/world/universalid.cpp | 4 ++ apps/opencs/model/world/universalid.hpp | 4 ++ apps/opencs/view/doc/view.cpp | 18 ++++++++ apps/opencs/view/doc/view.hpp | 4 ++ apps/opencs/view/world/subviews.cpp | 2 + components/esm/loaddial.cpp | 5 ++ components/esm/loaddial.hpp | 3 ++ 10 files changed, 136 insertions(+), 19 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 1e290d45f9..2af76cfa71 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -141,6 +141,12 @@ CSMWorld::Data::Data() : mRefs (mCells) mSpells.addColumn (new FlagColumn (Columns::ColumnId_StarterSpell, 0x2)); mSpells.addColumn (new FlagColumn (Columns::ColumnId_AlwaysSucceeds, 0x4)); + mTopics.addColumn (new StringIdColumn); + mTopics.addColumn (new RecordStateColumn); + + mJournals.addColumn (new StringIdColumn); + mJournals.addColumn (new RecordStateColumn); + mCells.addColumn (new StringIdColumn); mCells.addColumn (new RecordStateColumn); mCells.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Cell)); @@ -196,6 +202,8 @@ CSMWorld::Data::Data() : mRefs (mCells) addModel (new IdTable (&mRegions), UniversalId::Type_Regions, UniversalId::Type_Region); addModel (new IdTable (&mBirthsigns), UniversalId::Type_Birthsigns, UniversalId::Type_Birthsign); addModel (new IdTable (&mSpells), UniversalId::Type_Spells, UniversalId::Type_Spell); + addModel (new IdTable (&mTopics), UniversalId::Type_Topics, UniversalId::Type_Topic); + addModel (new IdTable (&mJournals), UniversalId::Type_Journals, UniversalId::Type_Journal); addModel (new IdTable (&mCells), UniversalId::Type_Cells, UniversalId::Type_Cell); addModel (new IdTable (&mReferenceables), UniversalId::Type_Referenceables, UniversalId::Type_Referenceable); @@ -319,6 +327,28 @@ CSMWorld::IdCollection& CSMWorld::Data::getSpells() return mSpells; } + +const CSMWorld::IdCollection& CSMWorld::Data::getTopcis() const +{ + return mTopics; +} + +CSMWorld::IdCollection& CSMWorld::Data::getTopcis() +{ + return mTopics; +} + +const CSMWorld::IdCollection& CSMWorld::Data::getJournals() const +{ + return mJournals; +} + +CSMWorld::IdCollection& CSMWorld::Data::getJournals() +{ + return mJournals; +} + + const CSMWorld::IdCollection& CSMWorld::Data::getCells() const { return mCells; @@ -447,6 +477,30 @@ void CSMWorld::Data::loadFile (const boost::filesystem::path& path, bool base) case ESM::REC_STAT: mReferenceables.load (reader, base, UniversalId::Type_Static); break; case ESM::REC_WEAP: mReferenceables.load (reader, base, UniversalId::Type_Weapon); break; + case ESM::REC_DIAL: + { + std::string id = reader.getHNOString ("NAME"); + + ESM::Dialogue record; + record.mId = id; + record.load (reader); + + if (record.mType==ESM::Dialogue::Journal) + { + mJournals.load (record, base); + } + else if (record.mType==ESM::Dialogue::Deleted) + { + /// \todo handle deleted records + } + else + { + mTopics.load (record, base); + } + + break; + } + default: /// \todo throw an exception instead, once all records are implemented @@ -469,6 +523,8 @@ bool CSMWorld::Data::hasId (const std::string& id) const getRegions().searchId (id)!=-1 || getBirthsigns().searchId (id)!=-1 || getSpells().searchId (id)!=-1 || + getTopcis().searchId (id)!=-1 || + getJournals().searchId (id)!=-1 || getCells().searchId (id)!=-1 || getReferenceables().searchId (id)!=-1; } @@ -487,6 +543,8 @@ std::vector CSMWorld::Data::getIds (bool listDeleted) const appendIds (ids, mRegions, listDeleted); appendIds (ids, mBirthsigns, listDeleted); appendIds (ids, mSpells, listDeleted); + appendIds (ids, mTopics, listDeleted); + appendIds (ids, mJournals, listDeleted); appendIds (ids, mCells, listDeleted); appendIds (ids, mReferenceables, listDeleted); diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index e900bb10fb..74304e029b 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -20,6 +20,7 @@ #include #include #include +#include #include "../filter/filter.hpp" @@ -48,6 +49,8 @@ namespace CSMWorld IdCollection mRegions; IdCollection mBirthsigns; IdCollection mSpells; + IdCollection mTopics; + IdCollection mJournals; IdCollection mCells; RefIdCollection mReferenceables; RefCollection mRefs; @@ -116,6 +119,14 @@ namespace CSMWorld IdCollection& getSpells(); + const IdCollection& getTopcis() const; + + IdCollection& getTopcis(); + + const IdCollection& getJournals() const; + + IdCollection& getJournals(); + const IdCollection& getCells() const; IdCollection& getCells(); diff --git a/apps/opencs/model/world/idcollection.hpp b/apps/opencs/model/world/idcollection.hpp index 72aafb91fe..b6ae04572c 100644 --- a/apps/opencs/model/world/idcollection.hpp +++ b/apps/opencs/model/world/idcollection.hpp @@ -15,6 +15,8 @@ namespace CSMWorld public: void load (ESM::ESMReader& reader, bool base); + + void load (const ESXRecordT& record, bool base); }; template @@ -53,29 +55,35 @@ namespace CSMWorld IdAccessorT().getId (record) = id; record.load (reader); - int index = this->searchId (IdAccessorT().getId (record)); + load (record, base); + } + } - if (index==-1) - { - // new record - Record record2; - record2.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; - (base ? record2.mBase : record2.mModified) = record; + template + void IdCollection::load (const ESXRecordT& record, bool base) + { + int index = this->searchId (IdAccessorT().getId (record)); - this->appendRecord (record2); - } + if (index==-1) + { + // new record + Record record2; + record2.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; + (base ? record2.mBase : record2.mModified) = record; + + this->appendRecord (record2); + } + else + { + // old record + Record record2 = Collection::getRecord (index); + + if (base) + record2.mBase = record; else - { - // old record - Record record2 = Collection::getRecord (index); + record2.setModified (record); - if (base) - record2.mBase = record; - else - record2.setModified (record); - - this->setRecord (index, record2); - } + this->setRecord (index, record2); } } } diff --git a/apps/opencs/model/world/universalid.cpp b/apps/opencs/model/world/universalid.cpp index c9edd0c162..6201a3cdaa 100644 --- a/apps/opencs/model/world/universalid.cpp +++ b/apps/opencs/model/world/universalid.cpp @@ -29,6 +29,8 @@ namespace { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Regions, "Regions", 0 }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Birthsigns, "Birthsigns", 0 }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Spells, "Spells", 0 }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Topics, "Topics", 0 }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Journals, "Journals", 0 }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Cells, "Cells", 0 }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Referenceables, "Referenceables", 0 }, @@ -54,6 +56,8 @@ namespace { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Region, "Region", ":./land.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Birthsign, "Birthsign", ":./birthsign.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Spell, "Spell", ":./spell.png" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Topic, "Topic", 0 }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Journal, "Journal", 0 }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Cell, "Cell", ":./cell.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Referenceable, "Referenceables", 0 }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Activator, "Activator", ":./activator.png" }, diff --git a/apps/opencs/model/world/universalid.hpp b/apps/opencs/model/world/universalid.hpp index 2466407331..ffd99e572f 100644 --- a/apps/opencs/model/world/universalid.hpp +++ b/apps/opencs/model/world/universalid.hpp @@ -87,6 +87,10 @@ namespace CSMWorld Type_RegionMap, Type_Filter, Type_Filters, + Type_Topics, + Type_Topic, + Type_Journals, + Type_Journal, Type_Scene }; diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index b29250d204..5713449f23 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -163,6 +163,14 @@ void CSVDoc::View::setupMechanicsMenu() QAction *spells = new QAction (tr ("Spells"), this); connect (spells, SIGNAL (triggered()), this, SLOT (addSpellsSubView())); mechanics->addAction (spells); + + QAction *topics = new QAction (tr ("Topics"), this); + connect (topics, SIGNAL (triggered()), this, SLOT (addTopicsSubView())); + mechanics->addAction (topics); + + QAction *journals = new QAction (tr ("Journals"), this); + connect (journals, SIGNAL (triggered()), this, SLOT (addJournalsSubView())); + mechanics->addAction (journals); } void CSVDoc::View::setupAssetsMenu() @@ -412,6 +420,16 @@ void CSVDoc::View::addSceneSubView() addSubView (CSMWorld::UniversalId::Type_Scene); } +void CSVDoc::View::addTopicsSubView() +{ + addSubView (CSMWorld::UniversalId::Type_Topics); +} + +void CSVDoc::View::addJournalsSubView() +{ + addSubView (CSMWorld::UniversalId::Type_Journals); +} + void CSVDoc::View::abortOperation (int type) { mDocument->abortOperation (type); diff --git a/apps/opencs/view/doc/view.hpp b/apps/opencs/view/doc/view.hpp index 6f3c38daaa..2a31d9d807 100644 --- a/apps/opencs/view/doc/view.hpp +++ b/apps/opencs/view/doc/view.hpp @@ -166,6 +166,10 @@ namespace CSVDoc void addSceneSubView(); + void addTopicsSubView(); + + void addJournalsSubView(); + void toggleShowStatusBar (bool show); }; } diff --git a/apps/opencs/view/world/subviews.cpp b/apps/opencs/view/world/subviews.cpp index 0e3465b388..417a7258fd 100644 --- a/apps/opencs/view/world/subviews.cpp +++ b/apps/opencs/view/world/subviews.cpp @@ -36,6 +36,8 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) CSMWorld::UniversalId::Type_Regions, CSMWorld::UniversalId::Type_Birthsigns, CSMWorld::UniversalId::Type_Spells, + CSMWorld::UniversalId::Type_Topics, + CSMWorld::UniversalId::Type_Journals, CSMWorld::UniversalId::Type_None // end marker }; diff --git a/components/esm/loaddial.cpp b/components/esm/loaddial.cpp index fb50d5e9f5..f6c63efd20 100644 --- a/components/esm/loaddial.cpp +++ b/components/esm/loaddial.cpp @@ -36,4 +36,9 @@ void Dialogue::save(ESMWriter &esm) } } + void Dialogue::blank() + { + mInfo.clear(); + } + } diff --git a/components/esm/loaddial.hpp b/components/esm/loaddial.hpp index 61f3f763de..0f4ceb6acf 100644 --- a/components/esm/loaddial.hpp +++ b/components/esm/loaddial.hpp @@ -35,6 +35,9 @@ struct Dialogue void load(ESMReader &esm); void save(ESMWriter &esm); + + void blank(); + ///< Set record to default state (does not touch the ID and does not change the type). }; } #endif From b138533bf307332852d907b181114f53b2bf4d91 Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Sun, 20 Oct 2013 17:21:09 +0200 Subject: [PATCH 088/434] renamed defaultfilter.omwaddon.project to defaultfilters. --- CMakeLists.txt | 2 +- files/opencs/defaultfilters.omwaddon.project | Bin 10958 -> 0 bytes 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 files/opencs/defaultfilters.omwaddon.project diff --git a/CMakeLists.txt b/CMakeLists.txt index df2986715b..cfb682cdb8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -320,7 +320,7 @@ configure_file(${OpenMW_SOURCE_DIR}/files/openmw.cfg configure_file(${OpenMW_SOURCE_DIR}/files/opencs.cfg "${OpenMW_BINARY_DIR}/opencs.cfg") -configure_file(${OpenMW_SOURCE_DIR}/files/opencs/defaultfilters.omwaddon.project +configure_file(${OpenMW_SOURCE_DIR}/files/opencs/defaultfilters "${OpenMW_BINARY_DIR}/resources/defaultfilters" COPYONLY) if (NOT WIN32 AND NOT APPLE) diff --git a/files/opencs/defaultfilters.omwaddon.project b/files/opencs/defaultfilters.omwaddon.project deleted file mode 100644 index 0ac3c8db4bd939b8b9d559ab4af4056fc0b8d80e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10958 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 F{s+fR@9+Qs From 75c5316ad779a2ac5e6b989aea03e752c8198e26 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 20 Oct 2013 17:26:09 +0200 Subject: [PATCH 089/434] added dialogue type column to topics table --- apps/opencs/model/world/columnbase.hpp | 3 ++- apps/opencs/model/world/columnimp.hpp | 27 ++++++++++++++++++++++++++ apps/opencs/model/world/columns.cpp | 7 +++++++ apps/opencs/model/world/columns.hpp | 1 + apps/opencs/model/world/data.cpp | 1 + apps/opencs/view/doc/viewmanager.cpp | 3 ++- 6 files changed, 40 insertions(+), 2 deletions(-) diff --git a/apps/opencs/model/world/columnbase.hpp b/apps/opencs/model/world/columnbase.hpp index c1b423c94c..9b8d7dafb8 100644 --- a/apps/opencs/model/world/columnbase.hpp +++ b/apps/opencs/model/world/columnbase.hpp @@ -43,7 +43,8 @@ namespace CSMWorld Display_CreatureType, Display_WeaponType, Display_RecordState, - Display_RefRecordType + Display_RefRecordType, + Display_DialogueType }; int mColumnId; diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index a13ac9a8a9..eec0a41e2a 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -1284,6 +1284,33 @@ namespace CSMWorld return true; } }; + + template + struct DialogueTypeColumn : public Column + { + DialogueTypeColumn() + : Column (Columns::ColumnId_DialogueType, ColumnBase::Display_DialogueType) + {} + + virtual QVariant get (const Record& record) const + { + return static_cast (record.get().mType); + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + + record2.mType = data.toInt(); + + record.setModified (record2); + } + + virtual bool isEditable() const + { + return false; + } + }; } #endif diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index ca37840ad6..ae4136bd92 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -159,6 +159,7 @@ namespace CSMWorld { ColumnId_DoorPositionXRot, "Teleport Rot X" }, { ColumnId_DoorPositionYRot, "Teleport Rot Y" }, { ColumnId_DoorPositionZRot, "Teleport Rot Z" }, + { ColumnId_DialogueType, "Dialogue Type" }, { ColumnId_UseValue1, "Use value 1" }, { ColumnId_UseValue2, "Use value 2" }, @@ -269,6 +270,11 @@ namespace "unknown", "none", "short", "integer", "long", "float", "string", 0 }; + static const char *sDialogueTypeEnums[] = + { + "Topic", "Voice", "Greeting", "Persuasion", 0 + }; + const char **getEnumNames (CSMWorld::Columns::ColumnId column) { switch (column) @@ -283,6 +289,7 @@ namespace case CSMWorld::Columns::ColumnId_WeaponType: return sWeaponTypes; case CSMWorld::Columns::ColumnId_Modification: return sModificationEnums; case CSMWorld::Columns::ColumnId_ValueType: return sVarTypeEnums; + case CSMWorld::Columns::ColumnId_DialogueType: return sDialogueTypeEnums; default: return 0; } diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index 9b26cac4c7..111931fa85 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -152,6 +152,7 @@ namespace CSMWorld ColumnId_DoorPositionXRot = 139, ColumnId_DoorPositionYRot = 140, ColumnId_DoorPositionZRot = 141, + ColumnId_DialogueType = 142, // Allocated to a separate value range, so we don't get a collision should we ever need // to extend the number of use values. diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 2af76cfa71..284e756f06 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -143,6 +143,7 @@ CSMWorld::Data::Data() : mRefs (mCells) mTopics.addColumn (new StringIdColumn); mTopics.addColumn (new RecordStateColumn); + mTopics.addColumn (new DialogueTypeColumn); mJournals.addColumn (new StringIdColumn); mJournals.addColumn (new RecordStateColumn); diff --git a/apps/opencs/view/doc/viewmanager.cpp b/apps/opencs/view/doc/viewmanager.cpp index 83cd93e5dd..a4849795b2 100644 --- a/apps/opencs/view/doc/viewmanager.cpp +++ b/apps/opencs/view/doc/viewmanager.cpp @@ -74,7 +74,8 @@ CSVDoc::ViewManager::ViewManager (CSMDoc::DocumentManager& documentManager) { CSMWorld::ColumnBase::Display_ArmorType, CSMWorld::Columns::ColumnId_ArmorType, false }, { CSMWorld::ColumnBase::Display_ClothingType, CSMWorld::Columns::ColumnId_ClothingType, false }, { CSMWorld::ColumnBase::Display_CreatureType, CSMWorld::Columns::ColumnId_CreatureType, false }, - { CSMWorld::ColumnBase::Display_WeaponType, CSMWorld::Columns::ColumnId_WeaponType, false } + { CSMWorld::ColumnBase::Display_WeaponType, CSMWorld::Columns::ColumnId_WeaponType, false }, + { CSMWorld::ColumnBase::Display_DialogueType, CSMWorld::Columns::ColumnId_DialogueType, false } }; for (std::size_t i=0; i Date: Mon, 21 Oct 2013 13:39:13 +0200 Subject: [PATCH 090/434] set dialogue type for newly created dialogue records --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/model/world/columnimp.hpp | 10 ++++-- apps/opencs/model/world/data.cpp | 1 + apps/opencs/view/world/dialoguecreator.cpp | 35 ++++++++++++++++++ apps/opencs/view/world/dialoguecreator.hpp | 41 ++++++++++++++++++++++ apps/opencs/view/world/subviews.cpp | 9 +++-- 6 files changed, 93 insertions(+), 5 deletions(-) create mode 100644 apps/opencs/view/world/dialoguecreator.cpp create mode 100644 apps/opencs/view/world/dialoguecreator.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index f918cfebfb..541b3b5a2c 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -66,7 +66,7 @@ opencs_units (view/world opencs_units_noqt (view/world dialoguesubview subviews enumdelegate vartypedelegate recordstatusdelegate idtypedelegate datadisplaydelegate - scripthighlighter idvalidator + scripthighlighter idvalidator dialoguecreator ) diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index eec0a41e2a..23c529b3e3 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -1288,8 +1288,9 @@ namespace CSMWorld template struct DialogueTypeColumn : public Column { - DialogueTypeColumn() - : Column (Columns::ColumnId_DialogueType, ColumnBase::Display_DialogueType) + DialogueTypeColumn (bool hidden = false) + : Column (Columns::ColumnId_DialogueType, ColumnBase::Display_DialogueType, + hidden ? 0 : ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue) {} virtual QVariant get (const Record& record) const @@ -1307,6 +1308,11 @@ namespace CSMWorld } virtual bool isEditable() const + { + return true; + } + + virtual bool isUserEditable() const { return false; } diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 284e756f06..70e0e891db 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -147,6 +147,7 @@ CSMWorld::Data::Data() : mRefs (mCells) mJournals.addColumn (new StringIdColumn); mJournals.addColumn (new RecordStateColumn); + mJournals.addColumn (new DialogueTypeColumn (true)); mCells.addColumn (new StringIdColumn); mCells.addColumn (new RecordStateColumn); diff --git a/apps/opencs/view/world/dialoguecreator.cpp b/apps/opencs/view/world/dialoguecreator.cpp new file mode 100644 index 0000000000..c16214283e --- /dev/null +++ b/apps/opencs/view/world/dialoguecreator.cpp @@ -0,0 +1,35 @@ + +#include "dialoguecreator.hpp" + +#include + +#include "../../model/world/data.hpp" +#include "../../model/world/commands.hpp" +#include "../../model/world/columns.hpp" +#include "../../model/world/idtable.hpp" + +void CSVWorld::DialogueCreator::configureCreateCommand (CSMWorld::CreateCommand& command) const +{ + int index = + dynamic_cast (*getData().getTableModel (getCollectionId())). + findColumnIndex (CSMWorld::Columns::ColumnId_DialogueType); + + command.addValue (index, mType); +} + +CSVWorld::DialogueCreator::DialogueCreator (CSMWorld::Data& data, QUndoStack& undoStack, + const CSMWorld::UniversalId& id, int type) +: GenericCreator (data, undoStack, id), mType (type) +{} + +CSVWorld::Creator *CSVWorld::TopicCreatorFactory::makeCreator (CSMWorld::Data& data, + QUndoStack& undoStack, const CSMWorld::UniversalId& id) const +{ + return new DialogueCreator (data, undoStack, id, ESM::Dialogue::Topic); +} + +CSVWorld::Creator *CSVWorld::JournalCreatorFactory::makeCreator (CSMWorld::Data& data, + QUndoStack& undoStack, const CSMWorld::UniversalId& id) const +{ + return new DialogueCreator (data, undoStack, id, ESM::Dialogue::Journal); +} \ No newline at end of file diff --git a/apps/opencs/view/world/dialoguecreator.hpp b/apps/opencs/view/world/dialoguecreator.hpp new file mode 100644 index 0000000000..26f866909a --- /dev/null +++ b/apps/opencs/view/world/dialoguecreator.hpp @@ -0,0 +1,41 @@ +#ifndef CSV_WORLD_DIALOGUECREATOR_H +#define CSV_WORLD_DIALOGUECREATOR_H + +#include "genericcreator.hpp" + +namespace CSVWorld +{ + class DialogueCreator : public GenericCreator + { + int mType; + + protected: + + virtual void configureCreateCommand (CSMWorld::CreateCommand& command) const; + + public: + + DialogueCreator (CSMWorld::Data& data, QUndoStack& undoStack, + const CSMWorld::UniversalId& id, int type); + }; + + class TopicCreatorFactory : public CreatorFactoryBase + { + public: + + virtual Creator *makeCreator (CSMWorld::Data& data, QUndoStack& undoStack, + const CSMWorld::UniversalId& id) const; + ///< The ownership of the returned Creator is transferred to the caller. + }; + + class JournalCreatorFactory : public CreatorFactoryBase + { + public: + + virtual Creator *makeCreator (CSMWorld::Data& data, QUndoStack& undoStack, + const CSMWorld::UniversalId& id) const; + ///< The ownership of the returned Creator is transferred to the caller. + }; +} + +#endif diff --git a/apps/opencs/view/world/subviews.cpp b/apps/opencs/view/world/subviews.cpp index 417a7258fd..3d98cf73ce 100644 --- a/apps/opencs/view/world/subviews.cpp +++ b/apps/opencs/view/world/subviews.cpp @@ -14,6 +14,7 @@ #include "referenceablecreator.hpp" #include "referencecreator.hpp" #include "scenesubview.hpp" +#include "dialoguecreator.hpp" void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) { @@ -36,8 +37,6 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) CSMWorld::UniversalId::Type_Regions, CSMWorld::UniversalId::Type_Birthsigns, CSMWorld::UniversalId::Type_Spells, - CSMWorld::UniversalId::Type_Topics, - CSMWorld::UniversalId::Type_Journals, CSMWorld::UniversalId::Type_None // end marker }; @@ -55,6 +54,12 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) manager.add (CSMWorld::UniversalId::Type_References, new CSVDoc::SubViewFactoryWithCreator >); + manager.add (CSMWorld::UniversalId::Type_Topics, + new CSVDoc::SubViewFactoryWithCreator); + + manager.add (CSMWorld::UniversalId::Type_Journal, + new CSVDoc::SubViewFactoryWithCreator); + // Subviews for editing/viewing individual records manager.add (CSMWorld::UniversalId::Type_Script, new CSVDoc::SubViewFactory); From c0e550143108e81ffcb6ee896e1ea0dfaeffaff8 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 21 Oct 2013 13:58:47 +0200 Subject: [PATCH 091/434] disallow the deletion of non-topic, non-journal dialogue records --- apps/opencs/view/world/table.cpp | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 6167c084a0..a58eb873f3 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -87,19 +87,33 @@ std::vector CSVWorld::Table::listDeletableSelectedIds() const { QModelIndex index = mProxyModel->mapToSource (mProxyModel->index (iter->row(), 0)); + // check record state CSMWorld::RecordBase::State state = static_cast ( mModel->data (mModel->index (index.row(), 1)).toInt()); - if (state!=CSMWorld::RecordBase::State_Deleted) + if (state==CSMWorld::RecordBase::State_Deleted) + continue; + + // check other columns (only relevant for a subset of the tables) + int dialogueTypeIndex = + mModel->searchColumnIndex (CSMWorld::Columns::ColumnId_DialogueType); + + if (dialogueTypeIndex!=-1) { - int columnIndex = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); + int type = mModel->data (mModel->index (index.row(), dialogueTypeIndex)).toInt(); - std::string id = mModel->data (mModel->index (index.row(), columnIndex)). - toString().toUtf8().constData(); - - deletableIds.push_back (id); + if (type!=ESM::Dialogue::Topic && type!=ESM::Dialogue::Journal) + continue; } + + // add the id to the collection + int columnIndex = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); + + std::string id = mModel->data (mModel->index (index.row(), columnIndex)). + toString().toUtf8().constData(); + + deletableIds.push_back (id); } } From dc12648a3efd8da82e148d2610536753434eff73 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 21 Oct 2013 14:26:54 +0200 Subject: [PATCH 092/434] add fixed dialogue records when creating a new omwgame file --- apps/opencs/model/doc/document.cpp | 73 ++++++++++++++++++++++++++++++ apps/opencs/model/world/data.cpp | 6 +-- apps/opencs/model/world/data.hpp | 4 +- 3 files changed, 78 insertions(+), 5 deletions(-) diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index d7138f6714..64f627b5aa 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -2137,6 +2137,79 @@ void CSMDoc::Document::createBase() getData().getSkills().add (record); } + + static const char *sVoice[] = + { + "Intruder", + "Attack", + "Hello", + "Thief", + "Alarm", + "Idle", + "Flee", + "Hit", + 0 + }; + + for (int i=0; sVoice[i]; ++i) + { + ESM::Dialogue record; + record.mId = sVoice[i]; + record.mType = ESM::Dialogue::Voice; + record.blank(); + + getData().getTopics().add (record); + } + + static const char *sGreetings[] = + { + "Greeting 0", + "Greeting 1", + "Greeting 2", + "Greeting 3", + "Greeting 4", + "Greeting 5", + "Greeting 6", + "Greeting 7", + "Greeting 8", + "Greeting 9", + 0 + }; + + for (int i=0; sGreetings[i]; ++i) + { + ESM::Dialogue record; + record.mId = sGreetings[i]; + record.mType = ESM::Dialogue::Greeting; + record.blank(); + + getData().getTopics().add (record); + } + + static const char *sPersuasion[] = + { + "Intimidate Success", + "Intimidate Fail", + "Service Refusal", + "Admire Success", + "Taunt Success", + "Bribe Success", + "Info Refusal", + "Admire Fail", + "Taunt Fail", + "Bribe Fail", + 0 + }; + + for (int i=0; sPersuasion[i]; ++i) + { + ESM::Dialogue record; + record.mId = sPersuasion[i]; + record.mType = ESM::Dialogue::Persuasion; + record.blank(); + + getData().getTopics().add (record); + } } CSMDoc::Document::Document (const std::vector& files, diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 70e0e891db..c854711b13 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -330,12 +330,12 @@ CSMWorld::IdCollection& CSMWorld::Data::getSpells() } -const CSMWorld::IdCollection& CSMWorld::Data::getTopcis() const +const CSMWorld::IdCollection& CSMWorld::Data::getTopics() const { return mTopics; } -CSMWorld::IdCollection& CSMWorld::Data::getTopcis() +CSMWorld::IdCollection& CSMWorld::Data::getTopics() { return mTopics; } @@ -525,7 +525,7 @@ bool CSMWorld::Data::hasId (const std::string& id) const getRegions().searchId (id)!=-1 || getBirthsigns().searchId (id)!=-1 || getSpells().searchId (id)!=-1 || - getTopcis().searchId (id)!=-1 || + getTopics().searchId (id)!=-1 || getJournals().searchId (id)!=-1 || getCells().searchId (id)!=-1 || getReferenceables().searchId (id)!=-1; diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index 74304e029b..cf31c94947 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -119,9 +119,9 @@ namespace CSMWorld IdCollection& getSpells(); - const IdCollection& getTopcis() const; + const IdCollection& getTopics() const; - IdCollection& getTopcis(); + IdCollection& getTopics(); const IdCollection& getJournals() const; From 3b85d970872afc58954c1f5b9e3ec2951b5a2fb5 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 21 Oct 2013 15:38:13 +0200 Subject: [PATCH 093/434] handle deleted dialogue records --- apps/opencs/model/world/data.cpp | 13 +++++++++- apps/opencs/model/world/idcollection.hpp | 32 +++++++++++++++++++++++- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index c854711b13..130ce334f7 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -493,7 +493,18 @@ void CSMWorld::Data::loadFile (const boost::filesystem::path& path, bool base) } else if (record.mType==ESM::Dialogue::Deleted) { - /// \todo handle deleted records + if (mJournals.tryDelete (id)) + { + /// \todo handle info records + } + else if (mTopics.tryDelete (id)) + { + /// \todo handle info records + } + else + { + /// \todo report deletion of non-existing record + } } else { diff --git a/apps/opencs/model/world/idcollection.hpp b/apps/opencs/model/world/idcollection.hpp index b6ae04572c..0d723bef1e 100644 --- a/apps/opencs/model/world/idcollection.hpp +++ b/apps/opencs/model/world/idcollection.hpp @@ -7,7 +7,6 @@ namespace CSMWorld { - /// \brief Single type collection of top level records template > class IdCollection : public Collection @@ -17,6 +16,11 @@ namespace CSMWorld void load (ESM::ESMReader& reader, bool base); void load (const ESXRecordT& record, bool base); + + bool tryDelete (const std::string& id); + ///< Try deleting \a id. If the id does not exist or can't be deleted the call is ignored. + /// + /// \return Has the ID been deleted? }; template @@ -86,6 +90,32 @@ namespace CSMWorld this->setRecord (index, record2); } } + + template + bool IdCollection::tryDelete (const std::string& id) + { + int index = this->searchId (id); + + if (index==-1) + return false; + + Record record = Collection::getRecord (index); + + if (record.isDeleted()) + return false; + + if (record.mState==RecordBase::State_ModifiedOnly) + { + Collection::removeRows (index, 1); + } + else + { + record.mState = RecordBase::State_Deleted; + setRecord (index, record); + } + + return true; + } } #endif From b23df42817a0ed45a460e2680b98d02b2e19ab1d Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Mon, 21 Oct 2013 16:06:16 +0200 Subject: [PATCH 094/434] Removed old comment. Changed to set resources path correctly. --- apps/opencs/editor.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 9abc9ee780..94faa713b6 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -89,8 +89,7 @@ void CS::Editor::setupDataFiles() dataDirs.insert (dataDirs.end(), dataLocal.begin(), dataLocal.end()); -// Adding Resources directory. First check if there is a file defaultfilters in the user path. - mDocumentManager.setResourceDir(variables["resources"].as()); + mDocumentManager.setResourceDir(mCfgMgr.getGlobalDataPath()); for (Files::PathContainer::const_iterator iter = dataDirs.begin(); iter != dataDirs.end(); ++iter) { From 743c6ea5b1bf1f10b6c429da6523f54d99c3eb0b Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 21 Oct 2013 16:47:32 +0200 Subject: [PATCH 095/434] save dialogue records --- apps/opencs/model/doc/saving.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/opencs/model/doc/saving.cpp b/apps/opencs/model/doc/saving.cpp index 2b0056e721..b756a9dc19 100644 --- a/apps/opencs/model/doc/saving.cpp +++ b/apps/opencs/model/doc/saving.cpp @@ -58,6 +58,13 @@ CSMDoc::Saving::Saving (Document& document, const boost::filesystem::path& proje appendStage (new WriteCollectionStage > (mDocument.getData().getSpells(), mState)); + /// \todo deal with info records for topcis and journals + appendStage (new WriteCollectionStage > + (mDocument.getData().getTopics(), mState)); + + appendStage (new WriteCollectionStage > + (mDocument.getData().getJournals(), mState)); + appendStage (new WriteRefIdCollectionStage (mDocument, mState)); From 88e09159c427761508244e3786d055ba93213cd0 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 21 Oct 2013 18:04:40 +0200 Subject: [PATCH 096/434] splitting off characters menu from mechanics menu (was getting too big) --- apps/opencs/view/doc/view.cpp | 50 ++++++++++++++++++++--------------- apps/opencs/view/doc/view.hpp | 2 ++ 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index 5713449f23..733f5b2bb1 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -136,41 +136,46 @@ void CSVDoc::View::setupMechanicsMenu() connect (gmsts, SIGNAL (triggered()), this, SLOT (addGmstsSubView())); mechanics->addAction (gmsts); - QAction *skills = new QAction (tr ("Skills"), this); - connect (skills, SIGNAL (triggered()), this, SLOT (addSkillsSubView())); - mechanics->addAction (skills); - - QAction *classes = new QAction (tr ("Classes"), this); - connect (classes, SIGNAL (triggered()), this, SLOT (addClassesSubView())); - mechanics->addAction (classes); - - QAction *factions = new QAction (tr ("Factions"), this); - connect (factions, SIGNAL (triggered()), this, SLOT (addFactionsSubView())); - mechanics->addAction (factions); - - QAction *races = new QAction (tr ("Races"), this); - connect (races, SIGNAL (triggered()), this, SLOT (addRacesSubView())); - mechanics->addAction (races); - QAction *scripts = new QAction (tr ("Scripts"), this); connect (scripts, SIGNAL (triggered()), this, SLOT (addScriptsSubView())); mechanics->addAction (scripts); - QAction *birthsigns = new QAction (tr ("Birthsigns"), this); - connect (birthsigns, SIGNAL (triggered()), this, SLOT (addBirthsignsSubView())); - mechanics->addAction (birthsigns); - QAction *spells = new QAction (tr ("Spells"), this); connect (spells, SIGNAL (triggered()), this, SLOT (addSpellsSubView())); mechanics->addAction (spells); +} + +void CSVDoc::View::setupCharacterMenu() +{ + QMenu *characters = menuBar()->addMenu (tr ("Characters")); + + QAction *skills = new QAction (tr ("Skills"), this); + connect (skills, SIGNAL (triggered()), this, SLOT (addSkillsSubView())); + characters->addAction (skills); + + QAction *classes = new QAction (tr ("Classes"), this); + connect (classes, SIGNAL (triggered()), this, SLOT (addClassesSubView())); + characters->addAction (classes); + + QAction *factions = new QAction (tr ("Factions"), this); + connect (factions, SIGNAL (triggered()), this, SLOT (addFactionsSubView())); + characters->addAction (factions); + + QAction *races = new QAction (tr ("Races"), this); + connect (races, SIGNAL (triggered()), this, SLOT (addRacesSubView())); + characters->addAction (races); + + QAction *birthsigns = new QAction (tr ("Birthsigns"), this); + connect (birthsigns, SIGNAL (triggered()), this, SLOT (addBirthsignsSubView())); + characters->addAction (birthsigns); QAction *topics = new QAction (tr ("Topics"), this); connect (topics, SIGNAL (triggered()), this, SLOT (addTopicsSubView())); - mechanics->addAction (topics); + characters->addAction (topics); QAction *journals = new QAction (tr ("Journals"), this); connect (journals, SIGNAL (triggered()), this, SLOT (addJournalsSubView())); - mechanics->addAction (journals); + characters->addAction (journals); } void CSVDoc::View::setupAssetsMenu() @@ -189,6 +194,7 @@ void CSVDoc::View::setupUi() setupViewMenu(); setupWorldMenu(); setupMechanicsMenu(); + setupCharacterMenu(); setupAssetsMenu(); } diff --git a/apps/opencs/view/doc/view.hpp b/apps/opencs/view/doc/view.hpp index 2a31d9d807..7c13e374fe 100644 --- a/apps/opencs/view/doc/view.hpp +++ b/apps/opencs/view/doc/view.hpp @@ -63,6 +63,8 @@ namespace CSVDoc void setupMechanicsMenu(); + void setupCharacterMenu(); + void setupAssetsMenu(); void setupUi(); From 5e1bdd605b37e808e4785074cc675c9f8ca680d6 Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Mon, 21 Oct 2013 18:15:26 +0200 Subject: [PATCH 097/434] Corrected formatting in document.hpp --- apps/opencs/model/doc/document.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/opencs/model/doc/document.hpp b/apps/opencs/model/doc/document.hpp index e6d1b0c872..fb929cca6d 100644 --- a/apps/opencs/model/doc/document.hpp +++ b/apps/opencs/model/doc/document.hpp @@ -43,7 +43,7 @@ namespace CSMDoc CSMTools::Tools mTools; boost::filesystem::path mProjectPath; Saving mSaving; - boost::filesystem::path mResDir; + boost::filesystem::path mResDir; // It is important that the undo stack is declared last, because on desctruction it fires a signal, that is connected to a slot, that is // using other member variables. Unfortunately this connection is cut only in the QObject destructor, which is way too late. @@ -119,3 +119,4 @@ namespace CSMDoc } #endif + From 70602c2c36b74164112f2b5118ac620b08488db5 Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Mon, 21 Oct 2013 18:24:16 +0200 Subject: [PATCH 098/434] Removed changes in the unimplemented copy ctor. --- apps/opencs/model/doc/document.cpp | 2 +- apps/opencs/model/doc/document.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index fd2b328612..b56460a032 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -2182,7 +2182,7 @@ CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, co else { boost::filesystem::path locCustomFiltersPath (configuration.getUserPath()); - locCustomFiltersPath /= "customfilters.omwaddon.project"; + locCustomFiltersPath /= "defaultfilters"; if (boost::filesystem::exists(locCustomFiltersPath)) { boost::filesystem::copy(locCustomFiltersPath, mProjectPath); diff --git a/apps/opencs/model/doc/document.hpp b/apps/opencs/model/doc/document.hpp index fb929cca6d..437b0c5131 100644 --- a/apps/opencs/model/doc/document.hpp +++ b/apps/opencs/model/doc/document.hpp @@ -50,7 +50,7 @@ namespace CSMDoc QUndoStack mUndoStack; // not implemented - Document (const Files::ConfigurationManager& configuration, const std::vector< boost::filesystem::path >& files, const boost::filesystem::path& savePath, bool new_); + Document (const Document&); Document& operator= (const Document&); void load (const std::vector::const_iterator& begin, From ce2c5582573118e19677690e2738d1913306db5d Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Mon, 21 Oct 2013 18:40:20 +0200 Subject: [PATCH 099/434] Missed a file. --- files/opencs/defaultfilters | Bin 0 -> 10958 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 files/opencs/defaultfilters diff --git a/files/opencs/defaultfilters b/files/opencs/defaultfilters new file mode 100644 index 0000000000000000000000000000000000000000..0ac3c8db4bd939b8b9d559ab4af4056fc0b8d80e GIT binary patch literal 10958 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 F{s+fR@9+Qs literal 0 HcmV?d00001 From 3146af34d642a28b15b55f7eb9999d8ac50168a0 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 21 Oct 2013 19:28:57 +0200 Subject: [PATCH 100/434] some fixes for the merged filter branch --- apps/opencs/editor.cpp | 2 +- apps/opencs/model/doc/document.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 94faa713b6..a05b515d3b 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -89,7 +89,7 @@ void CS::Editor::setupDataFiles() dataDirs.insert (dataDirs.end(), dataLocal.begin(), dataLocal.end()); - mDocumentManager.setResourceDir(mCfgMgr.getGlobalDataPath()); + mDocumentManager.setResourceDir (variables["resources"].as()); for (Files::PathContainer::const_iterator iter = dataDirs.begin(); iter != dataDirs.end(); ++iter) { diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index 34bdc056f8..cc886f9cc2 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -2258,7 +2258,7 @@ CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, co locCustomFiltersPath /= "defaultfilters"; if (boost::filesystem::exists(locCustomFiltersPath)) { - boost::filesystem::copy(locCustomFiltersPath, mProjectPath); + boost::filesystem::copy_file (locCustomFiltersPath, mProjectPath); } else { boost::filesystem::path filters(mResDir); filters /= "defaultfilters"; From aa61948801067f4d96cf0000c0862095275f4b27 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 22 Oct 2013 11:08:37 +0200 Subject: [PATCH 101/434] relaxed rules for new IDs when ID is also a user visible text --- apps/opencs/view/world/dialoguecreator.cpp | 2 +- apps/opencs/view/world/genericcreator.cpp | 4 ++-- apps/opencs/view/world/genericcreator.hpp | 2 +- apps/opencs/view/world/idvalidator.cpp | 20 +++++++++++++++----- apps/opencs/view/world/idvalidator.hpp | 5 ++++- 5 files changed, 23 insertions(+), 10 deletions(-) diff --git a/apps/opencs/view/world/dialoguecreator.cpp b/apps/opencs/view/world/dialoguecreator.cpp index c16214283e..3523d5e32b 100644 --- a/apps/opencs/view/world/dialoguecreator.cpp +++ b/apps/opencs/view/world/dialoguecreator.cpp @@ -19,7 +19,7 @@ void CSVWorld::DialogueCreator::configureCreateCommand (CSMWorld::CreateCommand& CSVWorld::DialogueCreator::DialogueCreator (CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id, int type) -: GenericCreator (data, undoStack, id), mType (type) +: GenericCreator (data, undoStack, id, true), mType (type) {} CSVWorld::Creator *CSVWorld::TopicCreatorFactory::makeCreator (CSMWorld::Data& data, diff --git a/apps/opencs/view/world/genericcreator.cpp b/apps/opencs/view/world/genericcreator.cpp index df43d6c5f5..ba2b3665a9 100644 --- a/apps/opencs/view/world/genericcreator.cpp +++ b/apps/opencs/view/world/genericcreator.cpp @@ -57,14 +57,14 @@ const CSMWorld::UniversalId& CSVWorld::GenericCreator::getCollectionId() const } CSVWorld::GenericCreator::GenericCreator (CSMWorld::Data& data, QUndoStack& undoStack, - const CSMWorld::UniversalId& id) + const CSMWorld::UniversalId& id, bool relaxedIdRules) : mData (data), mUndoStack (undoStack), mListId (id), mLocked (false) { mLayout = new QHBoxLayout; mLayout->setContentsMargins (0, 0, 0, 0); mId = new QLineEdit; - mId->setValidator (new IdValidator (this)); + mId->setValidator (new IdValidator (relaxedIdRules, this)); mLayout->addWidget (mId, 1); mCreate = new QPushButton ("Create"); diff --git a/apps/opencs/view/world/genericcreator.hpp b/apps/opencs/view/world/genericcreator.hpp index 6752d8591f..8dd2ca911c 100644 --- a/apps/opencs/view/world/genericcreator.hpp +++ b/apps/opencs/view/world/genericcreator.hpp @@ -51,7 +51,7 @@ namespace CSVWorld public: GenericCreator (CSMWorld::Data& data, QUndoStack& undoStack, - const CSMWorld::UniversalId& id); + const CSMWorld::UniversalId& id, bool relaxedIdRules = false); virtual void setEditLock (bool locked); diff --git a/apps/opencs/view/world/idvalidator.cpp b/apps/opencs/view/world/idvalidator.cpp index cf6e5d77ba..c685e44ee2 100644 --- a/apps/opencs/view/world/idvalidator.cpp +++ b/apps/opencs/view/world/idvalidator.cpp @@ -12,15 +12,25 @@ bool CSVWorld::IdValidator::isValid (const QChar& c, bool first) const return false; } -CSVWorld::IdValidator::IdValidator (QObject *parent) : QValidator (parent) {} +CSVWorld::IdValidator::IdValidator (bool relaxed, QObject *parent) +: QValidator (parent), mRelaxed (relaxed) +{} QValidator::State CSVWorld::IdValidator::validate (QString& input, int& pos) const { - bool first = true; - - for (QString::const_iterator iter (input.begin()); iter!=input.end(); ++iter, first = false) - if (!isValid (*iter, first)) + if (mRelaxed) + { + if (input.indexOf ('"')!=-1 || input.indexOf ("::")!=-1) return QValidator::Invalid; + } + else + { + bool first = true; + + for (QString::const_iterator iter (input.begin()); iter!=input.end(); ++iter, first = false) + if (!isValid (*iter, first)) + return QValidator::Invalid; + } return QValidator::Acceptable; } \ No newline at end of file diff --git a/apps/opencs/view/world/idvalidator.hpp b/apps/opencs/view/world/idvalidator.hpp index db0ecb27a7..8ca162440b 100644 --- a/apps/opencs/view/world/idvalidator.hpp +++ b/apps/opencs/view/world/idvalidator.hpp @@ -7,13 +7,16 @@ namespace CSVWorld { class IdValidator : public QValidator { + bool mRelaxed; + private: bool isValid (const QChar& c, bool first) const; public: - IdValidator (QObject *parent = 0); + IdValidator (bool relaxed = false, QObject *parent = 0); + ///< \param relaxed Relaxed rules for IDs that also functino as user visible text virtual State validate (QString& input, int& pos) const; From dc473221e79775dde811359cdb155add62ea5a1b Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 22 Oct 2013 11:21:12 +0200 Subject: [PATCH 102/434] added ID-argument to CollectionBase::getAppendIndex (required for info record collection) --- apps/opencs/model/world/collection.hpp | 6 ++++-- apps/opencs/model/world/collectionbase.hpp | 3 ++- apps/opencs/model/world/idtable.cpp | 4 ++-- apps/opencs/model/world/refidcollection.cpp | 2 +- apps/opencs/model/world/refidcollection.hpp | 2 +- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/apps/opencs/model/world/collection.hpp b/apps/opencs/model/world/collection.hpp index 84a00cef82..5ae53e7101 100644 --- a/apps/opencs/model/world/collection.hpp +++ b/apps/opencs/model/world/collection.hpp @@ -104,7 +104,8 @@ namespace CSMWorld virtual const Record& getRecord (int index) const; - virtual int getAppendIndex (UniversalId::Type type = UniversalId::Type_None) const; + virtual int getAppendIndex (const std::string& id, + UniversalId::Type type = UniversalId::Type_None) const; ///< \param type Will be ignored, unless the collection supports multiple record types virtual std::vector getIds (bool listDeleted = true) const; @@ -293,7 +294,8 @@ namespace CSMWorld } template - int Collection::getAppendIndex (UniversalId::Type type) const + int Collection::getAppendIndex (const std::string& id, + UniversalId::Type type) const { return static_cast (mRecords.size()); } diff --git a/apps/opencs/model/world/collectionbase.hpp b/apps/opencs/model/world/collectionbase.hpp index ff6dab247d..8045ea543f 100644 --- a/apps/opencs/model/world/collectionbase.hpp +++ b/apps/opencs/model/world/collectionbase.hpp @@ -76,7 +76,8 @@ namespace CSMWorld virtual const RecordBase& getRecord (int index) const = 0; - virtual int getAppendIndex (UniversalId::Type type = UniversalId::Type_None) const = 0; + virtual int getAppendIndex (const std::string& id, + UniversalId::Type type = UniversalId::Type_None) const = 0; ///< \param type Will be ignored, unless the collection supports multiple record types virtual std::vector getIds (bool listDeleted = true) const = 0; diff --git a/apps/opencs/model/world/idtable.cpp b/apps/opencs/model/world/idtable.cpp index baaf75289c..f37e3894e3 100644 --- a/apps/opencs/model/world/idtable.cpp +++ b/apps/opencs/model/world/idtable.cpp @@ -118,7 +118,7 @@ QModelIndex CSMWorld::IdTable::parent (const QModelIndex& index) const void CSMWorld::IdTable::addRecord (const std::string& id, UniversalId::Type type) { - int index = mIdCollection->getAppendIndex(); + int index = mIdCollection->getAppendIndex (id, type); beginInsertRows (QModelIndex(), index, index); @@ -138,7 +138,7 @@ void CSMWorld::IdTable::setRecord (const std::string& id, const RecordBase& reco if (index==-1) { - int index = mIdCollection->getAppendIndex(); + int index = mIdCollection->getAppendIndex (id); beginInsertRows (QModelIndex(), index, index); diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index cda2711cc7..b4352ef2e4 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -530,7 +530,7 @@ void CSMWorld::RefIdCollection::load (ESM::ESMReader& reader, bool base, Univers } } -int CSMWorld::RefIdCollection::getAppendIndex (UniversalId::Type type) const +int CSMWorld::RefIdCollection::getAppendIndex (const std::string& id, UniversalId::Type type) const { return mData.getAppendIndex (type); } diff --git a/apps/opencs/model/world/refidcollection.hpp b/apps/opencs/model/world/refidcollection.hpp index 22f83150de..e5e2059552 100644 --- a/apps/opencs/model/world/refidcollection.hpp +++ b/apps/opencs/model/world/refidcollection.hpp @@ -87,7 +87,7 @@ namespace CSMWorld void load (ESM::ESMReader& reader, bool base, UniversalId::Type type); - virtual int getAppendIndex (UniversalId::Type type) const; + virtual int getAppendIndex (const std::string& id, UniversalId::Type type) const; ///< \param type Will be ignored, unless the collection supports multiple record types virtual std::vector getIds (bool listDeleted) const; From 67bc0a0c70379ed703ffcfb513b78b090f51bc77 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 22 Oct 2013 11:32:10 +0200 Subject: [PATCH 103/434] additional modification to the IDValidator (restricting relaxed mode slightly more) --- apps/opencs/view/world/idvalidator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/view/world/idvalidator.cpp b/apps/opencs/view/world/idvalidator.cpp index c685e44ee2..7c210daaec 100644 --- a/apps/opencs/view/world/idvalidator.cpp +++ b/apps/opencs/view/world/idvalidator.cpp @@ -20,7 +20,7 @@ QValidator::State CSVWorld::IdValidator::validate (QString& input, int& pos) con { if (mRelaxed) { - if (input.indexOf ('"')!=-1 || input.indexOf ("::")!=-1) + if (input.indexOf ('"')!=-1 || input.indexOf ("::")!=-1 || input.indexOf ("#")!=-1) return QValidator::Invalid; } else From ca700c4cfd0a99aa1c750f23a4ebf5fe99988071 Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Tue, 22 Oct 2013 17:20:15 +0200 Subject: [PATCH 104/434] Creating the new branch for filters. --- manual/opencs/.gitignore | 4 ++++ manual/opencs/filters.tex | 13 +++++++++++++ manual/opencs/main.tex | 12 ++++++++++++ 3 files changed, 29 insertions(+) create mode 100644 manual/opencs/.gitignore create mode 100644 manual/opencs/filters.tex create mode 100644 manual/opencs/main.tex diff --git a/manual/opencs/.gitignore b/manual/opencs/.gitignore new file mode 100644 index 0000000000..12930f58e4 --- /dev/null +++ b/manual/opencs/.gitignore @@ -0,0 +1,4 @@ +*.backup +*.aux +*.log +*.toc \ No newline at end of file diff --git a/manual/opencs/filters.tex b/manual/opencs/filters.tex new file mode 100644 index 0000000000..5a9ca6d9ce --- /dev/null +++ b/manual/opencs/filters.tex @@ -0,0 +1,13 @@ +\section{Filters} +\subsection{Introduction} +Filters are the key element of OpenCS use cases by allowing rapid and easy access to the seeked records presented in all tables. Therefore: in order to use this application fully effective you should make sure that all concepts and instructions written in the this section of the manual are perfectly clear to you.\\ +Don't be afraid though, filters are fairly intuitive and easy to use. +\subsection{Used Terms} +\begin{description} + \item[Filter] is generally speaking a tool able to ``Filter'' (that is: select some elements, while discarding others) according to the some criteria. In case of OpenCS: records are being filtred according to the criteria of user choice. Criteria are written down in language with simple syntax. + \item[Criteria] describes condition under with any any record is being select by the filter. + \item[Syntax] as you may noticed computers (in general) are rather strict, and expect only strictly formulated orders -- that is: written with correct syntax. Our syntax is simple and described in the {B}asics subsection. + \item[Expression] is a criteria, only written with OpenCS filter syntax. +\end{description} +\subsection{Basics} +\subsection{Advanced Usage} \ No newline at end of file diff --git a/manual/opencs/main.tex b/manual/opencs/main.tex new file mode 100644 index 0000000000..da3457e133 --- /dev/null +++ b/manual/opencs/main.tex @@ -0,0 +1,12 @@ +\documentclass[american]{article} +\usepackage[T1]{fontenc} +\usepackage{babel} +\author{OpenMW Team} +\begin{document} + +\title{OpenCS User Manual} + +\maketitle +\tableofcontents{} +\input{filters} +\end{document} From 930aef7ae5025972f944874a06280d3af98a7bd2 Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Tue, 22 Oct 2013 21:25:13 +0200 Subject: [PATCH 105/434] Added *.pdf to the gitignore. --- manual/opencs/.gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/manual/opencs/.gitignore b/manual/opencs/.gitignore index 12930f58e4..cf62bd6fc6 100644 --- a/manual/opencs/.gitignore +++ b/manual/opencs/.gitignore @@ -1,4 +1,5 @@ *.backup *.aux *.log -*.toc \ No newline at end of file +*.toc +*.pdf \ No newline at end of file From f9591ddda61fec57783514e4b2bd860b86e39f09 Mon Sep 17 00:00:00 2001 From: graffy76 Date: Tue, 22 Oct 2013 21:52:35 -0500 Subject: [PATCH 106/434] Reimplemented constentselector view class --- apps/launcher/CMakeLists.txt | 11 + apps/launcher/datafilespage.cpp | 203 +++++++++---- apps/launcher/datafilespage.hpp | 33 ++- apps/launcher/maindialog.cpp | 2 +- .../launcher/utils/lineedit.cpp | 20 +- .../view => apps/launcher/utils}/lineedit.hpp | 33 ++- .../launcher/utils}/profilescombobox.cpp | 41 +-- apps/launcher/utils/profilescombobox.hpp | 40 +++ .../launcher/utils}/textinputdialog.cpp | 17 +- .../launcher/utils}/textinputdialog.hpp | 17 +- apps/opencs/CMakeLists.txt | 9 +- apps/opencs/editor.cpp | 6 +- .../opencs/view/doc}/adjusterwidget.cpp | 0 .../opencs/view/doc}/adjusterwidget.hpp | 0 apps/opencs/view/doc/filedialog.cpp | 111 ++++--- apps/opencs/view/doc/filedialog.hpp | 46 +-- .../opencs/view/doc}/filewidget.cpp | 0 .../opencs/view/doc}/filewidget.hpp | 0 apps/opencs/view/doc/newgame.cpp | 4 +- components/CMakeLists.txt | 8 +- .../contentselector/model/contentmodel.cpp | 10 + components/contentselector/view/combobox.cpp | 39 +++ components/contentselector/view/combobox.hpp | 30 ++ .../contentselector/view/comboboxlineedit.hpp | 37 --- .../contentselector/view/contentselector.cpp | 279 +----------------- .../contentselector/view/contentselector.hpp | 72 +---- components/contentselector/view/lineedit.cpp | 39 --- .../contentselector/view/profilescombobox.hpp | 42 --- files/ui/contentselector.ui | 111 +++++++ files/ui/datafilespage.ui | 203 +------------ files/ui/filedialog.ui | 73 +++++ 31 files changed, 695 insertions(+), 841 deletions(-) rename components/contentselector/view/comboboxlineedit.cpp => apps/launcher/utils/lineedit.cpp (58%) rename {components/contentselector/view => apps/launcher/utils}/lineedit.hpp (54%) rename {components/contentselector/view => apps/launcher/utils}/profilescombobox.cpp (60%) create mode 100644 apps/launcher/utils/profilescombobox.hpp rename {components/contentselector/view => apps/launcher/utils}/textinputdialog.cpp (77%) rename {components/contentselector/view => apps/launcher/utils}/textinputdialog.hpp (66%) rename {components/contentselector/view => apps/opencs/view/doc}/adjusterwidget.cpp (100%) rename {components/contentselector/view => apps/opencs/view/doc}/adjusterwidget.hpp (100%) rename {components/contentselector/view => apps/opencs/view/doc}/filewidget.cpp (100%) rename {components/contentselector/view => apps/opencs/view/doc}/filewidget.hpp (100%) create mode 100644 components/contentselector/view/combobox.cpp create mode 100644 components/contentselector/view/combobox.hpp delete mode 100644 components/contentselector/view/comboboxlineedit.hpp delete mode 100644 components/contentselector/view/lineedit.cpp delete mode 100644 components/contentselector/view/profilescombobox.hpp create mode 100644 files/ui/contentselector.ui create mode 100644 files/ui/filedialog.ui diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index 49dedd8290..18c555a249 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -11,6 +11,9 @@ set(LAUNCHER settings/launchersettings.cpp utils/checkablemessagebox.cpp + utils/profilescombobox.cpp + utils/textinputdialog.cpp + utils/lineedit.cpp ${CMAKE_SOURCE_DIR}/files/launcher/launcher.rc ) @@ -31,6 +34,9 @@ set(LAUNCHER_HEADER settings/settingsbase.hpp utils/checkablemessagebox.hpp + utils/profilescombobox.hpp + utils/textinputdialog.hpp + utils/lineedit.hpp ) if(NOT WIN32) LIST(APPEND LAUNCHER_HEADER unshieldthread.hpp) @@ -45,7 +51,11 @@ set(LAUNCHER_HEADER_MOC playpage.hpp textslotmsgbox.hpp + utils/textinputdialog.hpp utils/checkablemessagebox.hpp + utils/profilescombobox.hpp + utils/lineedit.hpp + ) if(NOT WIN32) @@ -58,6 +68,7 @@ set(LAUNCHER_UI ${CMAKE_SOURCE_DIR}/files/ui/graphicspage.ui ${CMAKE_SOURCE_DIR}/files/ui/mainwindow.ui ${CMAKE_SOURCE_DIR}/files/ui/playpage.ui + ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui ) source_group(launcher FILES ${LAUNCHER} ${LAUNCHER_HEADER}) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index a705ae37d8..2140caaf91 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -5,14 +5,16 @@ #include #include #include +#include #include #include -#include #include -#include + +#include "utils/textinputdialog.hpp" +#include "utils/profilescombobox.hpp" #include "settings/gamesettings.hpp" #include "settings/launchersettings.hpp" @@ -25,45 +27,30 @@ DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gam , mLauncherSettings(launcherSettings) , QWidget(parent) { + ui.setupUi (this); + setObjectName ("DataFilesPage"); + mSelector = new ContentSelectorView::ContentSelector (ui.contentSelectorWidget); - unsigned char flags; - - flags = ContentSelectorView::Flag_Content | ContentSelectorView::Flag_Profile; - - ContentSelectorView::ContentSelector::configure(this, flags); - mSelector = &ContentSelectorView::ContentSelector::instance(); - + buildView(); setupDataFiles(); - - - connect (mSelector, SIGNAL (signalProfileRenamed (QString, QString)), - this, SLOT (slotProfileRenamed (QString, QString))); - - connect (mSelector, SIGNAL (signalProfileChangedByUser (QString, QString)), - this, SLOT (slotProfileChangedByUser (QString, QString))); - - connect (mSelector, SIGNAL (signalProfileDeleted (QString)), - this, SLOT (slotProfileDeleted (QString))); - - connect (mSelector, SIGNAL (signalAddNewProfile (QString)), - this, SLOT (slotAddNewProfile (QString))); } void DataFilesPage::loadSettings() { - QString profileName = mSelector->getProfileText(); + QString profileName = ui.profilesComboBox->currentText(); QStringList files = mLauncherSettings.values(QString("Profiles/") + profileName + QString("/game"), Qt::MatchExactly); QStringList addons = mLauncherSettings.values(QString("Profiles/") + profileName + QString("/addon"), Qt::MatchExactly); mSelector->clearCheckStates(); - if (files.size() > 0) - mSelector->setGameFile(files.at(0)); - else - mSelector->setGameFile(); + QString gameFile (""); + if (files.size()>0) + gameFile = files.at (0); + + mSelector->setGameFile(gameFile); mSelector->setCheckStates(addons); } @@ -72,7 +59,7 @@ void DataFilesPage::saveSettings(const QString &profile) QString profileName = profile; if (profileName.isEmpty()) - profileName = mSelector->getProfileText(); + profileName = ui.profilesComboBox->currentText(); //retrieve the files selected for the profile ContentSelectorModel::ContentFileList items = mSelector->selectedFiles(); @@ -83,7 +70,7 @@ void DataFilesPage::saveSettings(const QString &profile) mGameSettings.remove(QString("addon")); //set the value of the current profile (not necessarily the profile being saved!) - mLauncherSettings.setValue(QString("Profiles/currentprofile"), mSelector->getProfileText()); + mLauncherSettings.setValue(QString("Profiles/currentprofile"), ui.profilesComboBox->currentText()); foreach(const ContentSelectorModel::EsmFile *item, items) { @@ -98,39 +85,64 @@ void DataFilesPage::saveSettings(const QString &profile) } +void DataFilesPage::buildView() +{ + ui.verticalLayout->insertWidget (0, mSelector->uiWidget()); + + //tool buttons + ui.newProfileButton->setToolTip ("Create a new profile"); + ui.deleteProfileButton->setToolTip ("Delete an existing profile"); + + //combo box + ui.profilesComboBox->addItem ("Default"); + ui.profilesComboBox->setPlaceholderText (QString("Select a profile...")); + + // Add the actions to the toolbuttons + ui.newProfileButton->setDefaultAction (ui.newProfileAction); + ui.deleteProfileButton->setDefaultAction (ui.deleteProfileAction); + + //establish connections + connect (ui.profilesComboBox, SIGNAL (currentIndexChanged(int)), + this, SLOT (slotProfileChanged(int))); + + connect (ui.profilesComboBox, SIGNAL (profileRenamed(QString, QString)), + this, SLOT (slotProfileRenamed(QString, QString))); + + connect (ui.profilesComboBox, SIGNAL (signalProfileChanged(QString, QString)), + this, SLOT (slotProfileChangedByUser(QString, QString))); +} + void DataFilesPage::removeProfile(const QString &profile) { mLauncherSettings.remove(QString("Profiles/") + profile + QString("/game")); mLauncherSettings.remove(QString("Profiles/") + profile + QString("/addon")); } -void DataFilesPage::changeProfiles(const QString &previous, const QString ¤t, bool savePrevious) +void DataFilesPage::setProfile(int index, bool savePrevious) { - //abort if no change (typically a duplicate signal) - if (previous == current) - return; + if (index >= -1 && index < ui.profilesComboBox->count()) + { + QString previous = ui.profilesComboBox->itemText(ui.profilesComboBox->currentIndex()); + QString current = ui.profilesComboBox->itemText(index); - int index = -1; - - if (!previous.isEmpty()) - index = mSelector->getProfileIndex(previous); - - // Store the previous profile if it exists - if ( (index != -1) && savePrevious) - saveSettings(previous); - - loadSettings(); + setProfile (previous, current, savePrevious); + } } -void DataFilesPage::slotAddNewProfile(const QString &profile) +void DataFilesPage::setProfile (const QString &previous, const QString ¤t, bool savePrevious) { - saveSettings(); - mSelector->clearCheckStates(); - mSelector->addProfile(profile, true); - mSelector->setGameFile(); - saveSettings(); + //abort if no change (poss. duplicate signal) + if (previous == current) + return; - emit signalProfileChanged(mSelector->getProfileIndex(profile)); + if (!previous.isEmpty() && savePrevious) + saveSettings (previous); + + ui.profilesComboBox->setCurrentIndex (ui.profilesComboBox->findText (current)); + + loadSettings(); + + checkForDefaultProfile(); } void DataFilesPage::slotProfileDeleted (const QString &item) @@ -140,8 +152,8 @@ void DataFilesPage::slotProfileDeleted (const QString &item) void DataFilesPage::slotProfileChangedByUser(const QString &previous, const QString ¤t) { - changeProfiles(previous, current); - emit signalProfileChanged(mSelector->getProfileIndex(current)); + setProfile(previous, current, true); + emit signalProfileChanged (ui.profilesComboBox->findText(current)); } void DataFilesPage::slotProfileRenamed(const QString &previous, const QString ¤t) @@ -160,7 +172,7 @@ void DataFilesPage::slotProfileRenamed(const QString &previous, const QString &c void DataFilesPage::slotProfileChanged(int index) { - mSelector->setProfile(index); + setProfile (index, true); } void DataFilesPage::setupDataFiles() @@ -178,21 +190,92 @@ void DataFilesPage::setupDataFiles() QStringList profiles = mLauncherSettings.subKeys(QString("Profiles/")); QString profile = mLauncherSettings.value(QString("Profiles/currentprofile")); - foreach (const QString &item, profiles) - mSelector->addProfile (item); + addProfile (item, false); - mSelector->addProfile (profile, true); + addProfile (profile, true); loadSettings(); } -QAbstractItemModel *DataFilesPage::profilesModel() const +void DataFilesPage::on_newProfileAction_triggered() { - return mSelector->profilesModel(); + TextInputDialog newDialog (tr("New Profile"), tr("Profile name:"), this); + + if (newDialog.exec() != QDialog::Accepted) + return; + + QString profile = newDialog.getText(); + + if (profile.isEmpty()) + return; + + saveSettings(); + + mSelector->clearCheckStates(); + + addProfile(profile, true); + + mSelector->setGameFile(); + + saveSettings(); + + emit signalProfileChanged (ui.profilesComboBox->findText(profile)); } -int DataFilesPage::profilesIndex() const +void DataFilesPage::addProfile (const QString &profile, bool setAsCurrent) { - return mSelector->getProfileIndex(mSelector->getProfileText()); + if (profile.isEmpty()) + return; + + if (ui.profilesComboBox->findText (profile) != -1) + return; + + ui.profilesComboBox->addItem (profile); + + if (setAsCurrent) + setProfile (ui.profilesComboBox->findText (profile), false); +} + +void DataFilesPage::on_deleteProfileAction_triggered() +{ + QString profile = ui.profilesComboBox->currentText(); + + if (profile.isEmpty()) + return; + + if (!showDeleteMessageBox (profile)) + return; + + // Remove the profile from the combobox + ui.profilesComboBox->removeItem (ui.profilesComboBox->findText (profile)); + + loadSettings(); + + checkForDefaultProfile(); +} + +void DataFilesPage::checkForDefaultProfile() +{ + //don't allow deleting "Default" profile + bool success = (ui.profilesComboBox->currentText() != "Default"); + + ui.deleteProfileAction->setEnabled (success); + ui.profilesComboBox->setEditEnabled (success); +} + +bool DataFilesPage::showDeleteMessageBox (const QString &text) +{ + QMessageBox msgBox(this); + msgBox.setWindowTitle(tr("Delete Profile")); + msgBox.setIcon(QMessageBox::Warning); + msgBox.setStandardButtons(QMessageBox::Cancel); + msgBox.setText(tr("Are you sure you want to delete %0?").arg(text)); + + QAbstractButton *deleteButton = + msgBox.addButton(tr("Delete"), QMessageBox::ActionRole); + + msgBox.exec(); + + return (msgBox.clickedButton() == deleteButton); } diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index 47e28493d0..cc054a4e47 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -1,6 +1,7 @@ #ifndef DATAFILESPAGE_H #define DATAFILESPAGE_H +#include "ui_datafilespage.h" #include #include @@ -12,7 +13,7 @@ class QMenu; class TextInputDialog; class GameSettings; class LauncherSettings; - +class ProfilesComboBox; namespace Files { struct ConfigurationManager; } namespace ContentSelectorView { class ContentSelector; } @@ -22,32 +23,37 @@ class DataFilesPage : public QWidget Q_OBJECT ContentSelectorView::ContentSelector *mSelector; + Ui::DataFilesPage ui; public: - DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gameSettings, LauncherSettings &launcherSettings, QWidget *parent = 0); + explicit DataFilesPage (Files::ConfigurationManager &cfg, GameSettings &gameSettings, + LauncherSettings &launcherSettings, QWidget *parent = 0); - QAbstractItemModel* profilesModel() const; - int profilesIndex() const; + QAbstractItemModel* profilesModel() const + { return ui.profilesComboBox->model(); } - void writeConfig(QString profile = QString()); + int profilesIndex() const + { return ui.profilesComboBox->currentIndex(); } + + //void writeConfig(QString profile = QString()); void saveSettings(const QString &profile = ""); void loadSettings(); signals: - void signalProfileChanged(int index); + void signalProfileChanged (int index); public slots: - //void showContextMenu(const QPoint &point); - + void slotProfileChanged (int index); private slots: - void slotAddNewProfile(const QString &profile); void slotProfileChangedByUser(const QString &previous, const QString ¤t); - void slotProfileChanged(int); void slotProfileRenamed(const QString &previous, const QString ¤t); void slotProfileDeleted(const QString &item); + void on_newProfileAction_triggered(); + void on_deleteProfileAction_triggered(); + private: QMenu *mContextMenu; @@ -59,11 +65,16 @@ private: void setPluginsCheckstates(Qt::CheckState state); + void buildView(); void setupDataFiles(); void setupConfig(); void readConfig(); + void setProfile (int index, bool savePrevious); + void setProfile (const QString &previous, const QString ¤t, bool savePrevious); void removeProfile (const QString &profile); - void changeProfiles(const QString &previous, const QString ¤t, bool savePrevious = true); + bool showDeleteMessageBox (const QString &text); + void addProfile (const QString &profile, bool setAsCurrent); + void checkForDefaultProfile(); }; #endif diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index fe9ca141ec..e514755fea 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -485,7 +485,7 @@ bool MainDialog::setupGameSettings() foreach (const QString path, mGameSettings.getDataDirs()) { QDir dir(path); QStringList filters; - filters << "*.esp" << "*.esm"; + filters << "*.esp" << "*.esm" << "*.omwgame" << "*.omwaddon"; if (!dir.entryList(filters).isEmpty()) dataDirs.append(path); diff --git a/components/contentselector/view/comboboxlineedit.cpp b/apps/launcher/utils/lineedit.cpp similarity index 58% rename from components/contentselector/view/comboboxlineedit.cpp rename to apps/launcher/utils/lineedit.cpp index df647a4a09..3487075808 100644 --- a/components/contentselector/view/comboboxlineedit.cpp +++ b/apps/launcher/utils/lineedit.cpp @@ -1,10 +1,12 @@ -#include -#include +#include "lineedit.hpp" -#include "comboboxlineedit.hpp" - -ContentSelectorView::ComboBoxLineEdit::ComboBoxLineEdit(QWidget *parent) +LineEdit::LineEdit(QWidget *parent) : QLineEdit(parent) +{ + setupClearButton(); +} + +void LineEdit::setupClearButton() { mClearButton = new QToolButton(this); QPixmap pixmap(":images/clear.png"); @@ -15,13 +17,9 @@ ContentSelectorView::ComboBoxLineEdit::ComboBoxLineEdit(QWidget *parent) mClearButton->hide(); connect(mClearButton, SIGNAL(clicked()), this, SLOT(clear())); connect(this, SIGNAL(textChanged(const QString&)), this, SLOT(updateClearButton(const QString&))); - int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); - - setObjectName(QString("ComboBoxLineEdit")); - setStyleSheet(QString("ComboBoxLineEdit { background-color: transparent; padding-right: %1px; } ").arg(mClearButton->sizeHint().width() + frameWidth + 1)); } -void ContentSelectorView::ComboBoxLineEdit::resizeEvent(QResizeEvent *) +void LineEdit::resizeEvent(QResizeEvent *) { QSize sz = mClearButton->sizeHint(); int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); @@ -29,7 +27,7 @@ void ContentSelectorView::ComboBoxLineEdit::resizeEvent(QResizeEvent *) (rect().bottom() + 1 - sz.height())/2); } -void ContentSelectorView::ComboBoxLineEdit::updateClearButton(const QString& text) +void LineEdit::updateClearButton(const QString& text) { mClearButton->setVisible(!text.isEmpty()); } diff --git a/components/contentselector/view/lineedit.hpp b/apps/launcher/utils/lineedit.hpp similarity index 54% rename from components/contentselector/view/lineedit.hpp rename to apps/launcher/utils/lineedit.hpp index ab1c37203a..2dd7da32bd 100644 --- a/components/contentselector/view/lineedit.hpp +++ b/apps/launcher/utils/lineedit.hpp @@ -11,29 +11,32 @@ #define LINEEDIT_H #include +#include +#include +#include class QToolButton; -namespace ContentSelectorView +class LineEdit : public QLineEdit { - class LineEdit : public QLineEdit - { - Q_OBJECT + Q_OBJECT - QString mPlaceholderText; + QString mPlaceholderText; - public: - LineEdit(QWidget *parent = 0); +public: + LineEdit(QWidget *parent = 0); - protected: - void resizeEvent(QResizeEvent *); +protected: + void resizeEvent(QResizeEvent *); - private slots: - void updateClearButton(const QString &text); +private slots: + void updateClearButton(const QString &text); + +protected: + QToolButton *mClearButton; + + void setupClearButton(); +}; - private: - QToolButton *mClearButton; - }; -} #endif // LIENEDIT_H diff --git a/components/contentselector/view/profilescombobox.cpp b/apps/launcher/utils/profilescombobox.cpp similarity index 60% rename from components/contentselector/view/profilescombobox.cpp rename to apps/launcher/utils/profilescombobox.cpp index 0e9905df45..c143307249 100644 --- a/components/contentselector/view/profilescombobox.cpp +++ b/apps/launcher/utils/profilescombobox.cpp @@ -5,23 +5,17 @@ #include #include "profilescombobox.hpp" -#include "comboboxlineedit.hpp" -ContentSelectorView::ProfilesComboBox::ProfilesComboBox(QWidget *parent) : - QComboBox(parent) +ProfilesComboBox::ProfilesComboBox(QWidget *parent) : + ContentSelectorView::ComboBox(parent) { - mValidator = new QRegExpValidator(QRegExp("^[a-zA-Z0-9_]*$"), this); // Alpha-numeric + underscore - setEditEnabled(true); - setValidator(mValidator); - setCompleter(0); - connect(this, SIGNAL(activated(int)), this, SLOT(slotIndexChangedByUser(int))); setInsertPolicy(QComboBox::NoInsert); } -void ContentSelectorView::ProfilesComboBox::setEditEnabled(bool editable) +void ProfilesComboBox::setEditEnabled(bool editable) { if (isEditable() == editable) return; @@ -37,6 +31,7 @@ void ContentSelectorView::ProfilesComboBox::setEditEnabled(bool editable) setValidator(mValidator); ComboBoxLineEdit *edit = new ComboBoxLineEdit(this); + setLineEdit(edit); setCompleter(0); @@ -50,7 +45,7 @@ void ContentSelectorView::ProfilesComboBox::setEditEnabled(bool editable) SIGNAL (signalProfileTextChanged (QString))); } -void ContentSelectorView::ProfilesComboBox::slotTextChanged(const QString &text) +void ProfilesComboBox::slotTextChanged(const QString &text) { QPalette *palette = new QPalette(); palette->setColor(QPalette::Text,Qt::red); @@ -64,7 +59,7 @@ void ContentSelectorView::ProfilesComboBox::slotTextChanged(const QString &text) } } -void ContentSelectorView::ProfilesComboBox::slotEditingFinished() +void ProfilesComboBox::slotEditingFinished() { QString current = currentText(); QString previous = itemText(currentIndex()); @@ -85,7 +80,7 @@ void ContentSelectorView::ProfilesComboBox::slotEditingFinished() emit(profileRenamed(previous, current)); } -void ContentSelectorView::ProfilesComboBox::slotIndexChangedByUser(int index) +void ProfilesComboBox::slotIndexChangedByUser(int index) { if (index == -1) return; @@ -94,23 +89,11 @@ void ContentSelectorView::ProfilesComboBox::slotIndexChangedByUser(int index) mOldProfile = currentText(); } -void ContentSelectorView::ProfilesComboBox::paintEvent(QPaintEvent *) +ProfilesComboBox::ComboBoxLineEdit::ComboBoxLineEdit (QWidget *parent) + : LineEdit (parent) { - QStylePainter painter(this); - painter.setPen(palette().color(QPalette::Text)); + int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); - // draw the combobox frame, focusrect and selected etc. - QStyleOptionComboBox opt; - initStyleOption(&opt); - painter.drawComplexControl(QStyle::CC_ComboBox, opt); - - // draw the icon and text - if (!opt.editable && currentIndex() == -1) // <<< we adjust the text displayed when nothing is selected - opt.currentText = mPlaceholderText; - painter.drawControl(QStyle::CE_ComboBoxLabel, opt); -} - -void ContentSelectorView::ProfilesComboBox::setPlaceholderText(const QString &text) -{ - mPlaceholderText = text; + setObjectName(QString("ComboBoxLineEdit")); + setStyleSheet(QString("ComboBoxLineEdit { background-color: transparent; padding-right: %1px; } ").arg(mClearButton->sizeHint().width() + frameWidth + 1)); } diff --git a/apps/launcher/utils/profilescombobox.hpp b/apps/launcher/utils/profilescombobox.hpp new file mode 100644 index 0000000000..1e27f66a9c --- /dev/null +++ b/apps/launcher/utils/profilescombobox.hpp @@ -0,0 +1,40 @@ +#ifndef PROFILESCOMBOBOX_HPP +#define PROFILESCOMBOBOX_HPP + +#include "components/contentselector/view/combobox.hpp" +#include "lineedit.hpp" + +class QString; + +class ProfilesComboBox : public ContentSelectorView::ComboBox +{ + Q_OBJECT + +public: + class ComboBoxLineEdit : public LineEdit + { + public: + explicit ComboBoxLineEdit (QWidget *parent = 0); + }; + +public: + + explicit ProfilesComboBox(QWidget *parent = 0); + void setEditEnabled(bool editable); + +signals: + void signalProfileTextChanged(const QString &item); + void signalProfileChanged(const QString &previous, const QString ¤t); + void signalProfileChanged(int index); + void profileRenamed(const QString &oldName, const QString &newName); + +private slots: + + void slotEditingFinished(); + void slotIndexChangedByUser(int index); + void slotTextChanged(const QString &text); + +private: + QString mOldProfile; +}; +#endif // PROFILESCOMBOBOX_HPP diff --git a/components/contentselector/view/textinputdialog.cpp b/apps/launcher/utils/textinputdialog.cpp similarity index 77% rename from components/contentselector/view/textinputdialog.cpp rename to apps/launcher/utils/textinputdialog.cpp index 6bb92f113e..9957e7dda8 100644 --- a/components/contentselector/view/textinputdialog.cpp +++ b/apps/launcher/utils/textinputdialog.cpp @@ -7,8 +7,6 @@ #include #include -#include - TextInputDialog::TextInputDialog(const QString& title, const QString &text, QWidget *parent) : QDialog(parent) { @@ -20,7 +18,7 @@ TextInputDialog::TextInputDialog(const QString& title, const QString &text, QWid // Line edit QValidator *validator = new QRegExpValidator(QRegExp("^[a-zA-Z0-9_]*$"), this); // Alpha-numeric + underscore - mLineEdit = new ContentSelectorView::LineEdit(this); + mLineEdit = new DialogLineEdit(this); mLineEdit->setValidator(validator); mLineEdit->setCompleter(0); @@ -74,3 +72,16 @@ void TextInputDialog::slotUpdateOkButton(QString text) mLineEdit->setPalette(*palette); } } + +TextInputDialog::DialogLineEdit::DialogLineEdit (QWidget *parent) : + LineEdit (parent) +{ + int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); + + setObjectName(QString("LineEdit")); + setStyleSheet(QString("LineEdit { padding-right: %1px; } ").arg(mClearButton->sizeHint().width() + frameWidth + 1)); + QSize msz = minimumSizeHint(); + setMinimumSize(qMax(msz.width(), mClearButton->sizeHint().height() + frameWidth * 2 + 2), + qMax(msz.height(), mClearButton->sizeHint().height() + frameWidth * 2 + 2)); + +} diff --git a/components/contentselector/view/textinputdialog.hpp b/apps/launcher/utils/textinputdialog.hpp similarity index 66% rename from components/contentselector/view/textinputdialog.hpp rename to apps/launcher/utils/textinputdialog.hpp index a0b7e350a5..148bbd1522 100644 --- a/components/contentselector/view/textinputdialog.hpp +++ b/apps/launcher/utils/textinputdialog.hpp @@ -3,23 +3,30 @@ #include +#include "lineedit.hpp" + class QDialogButtonBox; -namespace ContentSelectorView { - class LineEdit; -} - +class LineEdit; class TextInputDialog : public QDialog { Q_OBJECT - ContentSelectorView::LineEdit *mLineEdit; + class DialogLineEdit : public LineEdit + { + public: + explicit DialogLineEdit (QWidget *parent = 0); + }; + + DialogLineEdit *mLineEdit; QDialogButtonBox *mButtonBox; public: explicit TextInputDialog(const QString& title, const QString &text, QWidget *parent = 0); + ~TextInputDialog () {} + QString getText() const; int exec(); diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 9ae12c7a73..bc78352d4e 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -44,6 +44,7 @@ opencs_units_noqt (model/tools opencs_units (view/doc viewmanager view operations operation subview startup filedialog newgame + filewidget adjusterwidget ) @@ -122,11 +123,13 @@ opencs_units (view/filter set (OPENCS_US ) -set (OPENCS_RES ../../files/opencs/resources.qrc - ../../files/launcher/launcher.qrc +set (OPENCS_RES ${CMAKE_SOURCE_DIR}/files/opencs/resources.qrc + ${CMAKE_SOURCE_DIR}/files/launcher/launcher.qrc ) -set (OPENCS_UI ../../files/ui/datafilespage.ui +set (OPENCS_UI + ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui + ${CMAKE_SOURCE_DIR}/files/ui/filedialog.ui ) source_group (opencs FILES ${OPENCS_SRC} ${OPENCS_HDR}) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index ae10ec6422..e78357bc54 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -109,15 +109,13 @@ void CS::Editor::createGame() void CS::Editor::createAddon() { mStartup.hide(); - - mFileDialog.newFile(); + mFileDialog.showDialog (CSVDoc::FileDialog::DialogType_New); } void CS::Editor::loadDocument() { mStartup.hide(); - - mFileDialog.openFile(); + mFileDialog.showDialog (CSVDoc::FileDialog::DialogType_Open); } void CS::Editor::openFiles() diff --git a/components/contentselector/view/adjusterwidget.cpp b/apps/opencs/view/doc/adjusterwidget.cpp similarity index 100% rename from components/contentselector/view/adjusterwidget.cpp rename to apps/opencs/view/doc/adjusterwidget.cpp diff --git a/components/contentselector/view/adjusterwidget.hpp b/apps/opencs/view/doc/adjusterwidget.hpp similarity index 100% rename from components/contentselector/view/adjusterwidget.hpp rename to apps/opencs/view/doc/adjusterwidget.hpp diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index 2017ab1036..49e90ec813 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -12,81 +12,120 @@ #include #include -#include -#include +#include "components/contentselector/model/esmfile.hpp" #include "components/contentselector/view/contentselector.hpp" +#include "filewidget.hpp" +#include "adjusterwidget.hpp" #include CSVDoc::FileDialog::FileDialog(QWidget *parent) : - QDialog(parent), - mOpenFileFlags (ContentSelectorView::Flag_Content | ContentSelectorView::Flag_LoadAddon), - mNewFileFlags (ContentSelectorView::Flag_Content | ContentSelectorView::Flag_NewAddon) - + QDialog(parent), mSelector (0) { + ui.setupUi (this); resize(400, 400); + + setObjectName ("FileDialog"); + mSelector = new ContentSelectorView::ContentSelector (ui.contentSelectorWidget); } void CSVDoc::FileDialog::addFiles(const QString &path) { - ContentSelectorView::ContentSelector::addFiles(path); -} - -QString CSVDoc::FileDialog::filename() -{ - return ContentSelectorView::ContentSelector::instance().projectFilename(); + mSelector->addFiles(path); } QStringList CSVDoc::FileDialog::selectedFilePaths() { QStringList filePaths; - foreach (ContentSelectorModel::EsmFile *file, ContentSelectorView::ContentSelector:: - instance().selectedFiles() ) - { + foreach (ContentSelectorModel::EsmFile *file, mSelector->selectedFiles() ) filePaths.append(file->path()); - } + return filePaths; } -void CSVDoc::FileDialog::showDialog() +void CSVDoc::FileDialog::showDialog(DialogType dialogType) { + mDialogType = dialogType; + + switch (mDialogType) + { + case DialogType_New: + buildNewFileView(); + break; + + case DialogType_Open: + buildOpenFileView(); + break; + default: + break; + } + show(); raise(); activateWindow(); } -void CSVDoc::FileDialog::openFile() +void CSVDoc::FileDialog::buildNewFileView() { - setWindowTitle(tr("Open")); + setWindowTitle(tr("Create a new addon")); - ContentSelectorView::ContentSelector::configure(this, mOpenFileFlags); + QPushButton* createButton = ui.projectButtonBox->button (QDialogButtonBox::Ok); + createButton->setText ("Create"); + createButton->setEnabled (false); - connect (&ContentSelectorView::ContentSelector::instance(), - SIGNAL (accepted()), this, SIGNAL (openFiles())); + mFileWidget = new FileWidget (this); - connect (&ContentSelectorView::ContentSelector::instance(), - SIGNAL (rejected()), this, SLOT (slotRejected())); + mFileWidget->setType (true); + mFileWidget->extensionLabelIsVisible(true); - showDialog(); + ui.projectGroupBoxLayout->insertWidget (0, mFileWidget); + + connect (mFileWidget, SIGNAL (nameChanged(const QString &, bool)), + this, SLOT (slotUpdateCreateButton(const QString &, bool))); + + connect (mSelector, SIGNAL (signalCurrentGamefileIndexChanged (int)), + this, SLOT (slotUpdateCreateButton (int))); + + connect (ui.projectButtonBox, SIGNAL (accepted()), this, SIGNAL (createNewFile())); + + connect (ui.projectButtonBox, SIGNAL (rejected()), this, SLOT (slotRejected())); } -void CSVDoc::FileDialog::newFile() +void CSVDoc::FileDialog::buildOpenFileView() { - setWindowTitle(tr("New")); + setWindowTitle(tr("Open")); + ui.projectGroupBox->setTitle (QString("")); - ContentSelectorView::ContentSelector::configure(this, mNewFileFlags); - - connect (&ContentSelectorView::ContentSelector::instance(), - SIGNAL (accepted()), this, SIGNAL (createNewFile())); - - connect (&ContentSelectorView::ContentSelector::instance(), - SIGNAL (rejected()), this, SLOT (slotRejected())); - - showDialog(); + connect (ui.projectButtonBox, SIGNAL (accepted()), this, SIGNAL (openFiles())); + connect (ui.projectButtonBox, SIGNAL (rejected()), this , SIGNAL (rejected())); } void CSVDoc::FileDialog::slotRejected() { close(); } + +void CSVDoc::FileDialog::slotUpdateCreateButton (int) +{ + slotUpdateCreateButton (mFileWidget->getName(), true); +} + +void CSVDoc::FileDialog::slotUpdateCreateButton(const QString &name, bool) +{ + if (!(mDialogType == DialogType_New)) + return; + + bool success = (!name.isEmpty() && mSelector->selectedFiles().size() > 0); + + ui.projectButtonBox->button (QDialogButtonBox::Ok)->setEnabled (success); +} + +QString CSVDoc::FileDialog::filename() const +{ + if (mDialogType == DialogType_New) + return mFileWidget->getName(); + + return QString (""); +} + diff --git a/apps/opencs/view/doc/filedialog.hpp b/apps/opencs/view/doc/filedialog.hpp index acc35189dc..78883791e4 100644 --- a/apps/opencs/view/doc/filedialog.hpp +++ b/apps/opencs/view/doc/filedialog.hpp @@ -3,49 +3,54 @@ #include #include -#include "../../../../components/contentselector/view/contentselector.hpp" -class QDialogButtonBox; -class QSortFilterProxyModel; -class QAbstractItemModel; -class QPushButton; -class QStringList; -class QString; -class QMenu; -class QLabel; +#include "ui_filedialog.h" class DataFilesModel; class PluginsProxyModel; - - namespace ContentSelectorView { - class LineEdit; + class ContentSelector; } namespace CSVDoc { + class FileWidget; + class FileDialog : public QDialog { Q_OBJECT - unsigned char mOpenFileFlags; - unsigned char mNewFileFlags; + public: + + enum DialogType + { + DialogType_New, + DialogType_Open + }; + + private: + + ContentSelectorView::ContentSelector *mSelector; + Ui::FileDialog ui; + DialogType mDialogType; + FileWidget *mFileWidget; public: - explicit FileDialog(QWidget *parent = 0); - void openFile(); - void newFile(); + explicit FileDialog(QWidget *parent = 0); + void showDialog (DialogType dialogType); + void addFiles (const QString &path); - QString filename(); + QString filename() const; QStringList selectedFilePaths(); private: - void showDialog(); + void buildNewFileView(); + void buildOpenFileView(); signals: @@ -58,6 +63,9 @@ namespace CSVDoc private slots: + void slotUpdateCreateButton (int); + void slotUpdateCreateButton (const QString &, bool); + }; } #endif // FILEDIALOG_HPP diff --git a/components/contentselector/view/filewidget.cpp b/apps/opencs/view/doc/filewidget.cpp similarity index 100% rename from components/contentselector/view/filewidget.cpp rename to apps/opencs/view/doc/filewidget.cpp diff --git a/components/contentselector/view/filewidget.hpp b/apps/opencs/view/doc/filewidget.hpp similarity index 100% rename from components/contentselector/view/filewidget.hpp rename to apps/opencs/view/doc/filewidget.hpp diff --git a/apps/opencs/view/doc/newgame.cpp b/apps/opencs/view/doc/newgame.cpp index 265b983056..98681c499d 100644 --- a/apps/opencs/view/doc/newgame.cpp +++ b/apps/opencs/view/doc/newgame.cpp @@ -7,8 +7,8 @@ #include #include -#include "components/contentselector/view/filewidget.hpp" -#include "components/contentselector/view/adjusterwidget.hpp" +#include "filewidget.hpp" +#include "adjusterwidget.hpp" CSVDoc::NewGameDialogue::NewGameDialogue() { diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 103c6f4126..acb70b04db 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -74,7 +74,7 @@ add_component_dir (loadinglistener loadinglistener ) -set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/datafilespage.ui +set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui ) find_package(Qt4 COMPONENTS QtCore QtGui) @@ -82,11 +82,7 @@ if(QT_QTGUI_LIBRARY AND QT_QTCORE_LIBRARY) add_component_qt_dir (contentselector model/modelitem model/esmfile model/naturalsort model/contentmodel - view/profilescombobox view/comboboxlineedit - view/lineedit view/contentselector - view/filewidget view/adjusterwidget - view/textinputdialog - + view/combobox view/contentselector ) include(${QT_USE_FILE}) diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 8bb052d3dc..accc149cbb 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -399,6 +399,10 @@ void ContentSelectorModel::ContentModel::addFile(EsmFile *file) beginInsertRows(QModelIndex(), mFiles.count(), mFiles.count()); mFiles.append(file); endInsertRows(); + + QModelIndex idx = index (mFiles.size() - 2, 0, QModelIndex()); + + emit dataChanged (idx, idx); } void ContentSelectorModel::ContentModel::addFiles(const QString &path) @@ -466,6 +470,7 @@ void ContentSelectorModel::ContentModel::sortFiles() //iterate each file, obtaining a reference to it's gamefiles list for (int i = 0; i < fileCount; i++) { + QModelIndex idx1 = index (i, 0, QModelIndex()); const QStringList &gamefiles = mFiles.at(i)->gameFiles(); //iterate each file after the current file, verifying that none of it's //dependencies appear. @@ -474,6 +479,11 @@ void ContentSelectorModel::ContentModel::sortFiles() if (gamefiles.contains(mFiles.at(j)->fileName())) { mFiles.move(j, i); + + QModelIndex idx2 = index (j, 0, QModelIndex()); + + emit dataChanged (idx1, idx2); + movedFiles = true; } } diff --git a/components/contentselector/view/combobox.cpp b/components/contentselector/view/combobox.cpp new file mode 100644 index 0000000000..1d773b62dd --- /dev/null +++ b/components/contentselector/view/combobox.cpp @@ -0,0 +1,39 @@ +#include +#include +#include +#include +#include + +#include "combobox.hpp" + +ContentSelectorView::ComboBox::ComboBox(QWidget *parent) : + QComboBox(parent) +{ + mValidator = new QRegExpValidator(QRegExp("^[a-zA-Z0-9_]*$"), this); // Alpha-numeric + underscore + setValidator(mValidator); + setCompleter(0); + setEnabled (true); + + setInsertPolicy(QComboBox::NoInsert); +} + +void ContentSelectorView::ComboBox::paintEvent(QPaintEvent *) +{ + QStylePainter painter(this); + painter.setPen(palette().color(QPalette::Text)); + + // draw the combobox frame, focusrect and selected etc. + QStyleOptionComboBox opt; + initStyleOption(&opt); + painter.drawComplexControl(QStyle::CC_ComboBox, opt); + + // draw the icon and text + if (!opt.editable && currentIndex() == -1) // <<< we adjust the text displayed when nothing is selected + opt.currentText = mPlaceholderText; + painter.drawControl(QStyle::CE_ComboBoxLabel, opt); +} + +void ContentSelectorView::ComboBox::setPlaceholderText(const QString &text) +{ + mPlaceholderText = text; +} diff --git a/components/contentselector/view/combobox.hpp b/components/contentselector/view/combobox.hpp new file mode 100644 index 0000000000..e3888af2c7 --- /dev/null +++ b/components/contentselector/view/combobox.hpp @@ -0,0 +1,30 @@ +#ifndef COMBOBOX_HPP +#define COMBOBOX_HPP + +#include +#include + +class QString; +class QRegExpValidator; + +namespace ContentSelectorView +{ + class ComboBox : public QComboBox + { + Q_OBJECT + + public: + explicit ComboBox (QWidget *parent = 0); + + void setPlaceholderText(const QString &text); + + private: + QString mPlaceholderText; + + protected: + void paintEvent(QPaintEvent *); + QRegExpValidator *mValidator; + }; +} + +#endif // COMBOBOX_HPP diff --git a/components/contentselector/view/comboboxlineedit.hpp b/components/contentselector/view/comboboxlineedit.hpp deleted file mode 100644 index 1aef2f57b6..0000000000 --- a/components/contentselector/view/comboboxlineedit.hpp +++ /dev/null @@ -1,37 +0,0 @@ -/**************************************************************************** -** -** Copyright (c) 2007 Trolltech ASA -** -** Use, modification and distribution is allowed without limitation, -** warranty, liability or support of any kind. -** -****************************************************************************/ - -#ifndef LINEEDIT_H -#define LINEEDIT_H - -#include - -class QToolButton; - -namespace ContentSelectorView -{ - class ComboBoxLineEdit : public QLineEdit - { - Q_OBJECT - - public: - ComboBoxLineEdit(QWidget *parent = 0); - - protected: - void resizeEvent(QResizeEvent *); - - private slots: - void updateClearButton(const QString &text); - - private: - QToolButton *mClearButton; - }; -} -#endif // LIENEDIT_H - diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index 6965c948ec..7a7e1fd9d5 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -1,7 +1,6 @@ #include "contentselector.hpp" #include "../model/esmfile.hpp" -#include "lineedit.hpp" #include @@ -11,45 +10,16 @@ #include #include -#include "filewidget.hpp" -#include "adjusterwidget.hpp" -#include "textinputdialog.hpp" - #include -ContentSelectorView::ContentSelector *ContentSelectorView::ContentSelector::mInstance = 0; -QStringList ContentSelectorView::ContentSelector::mFilePaths; - -void ContentSelectorView::ContentSelector::configure(QWidget *subject, unsigned char flags) +ContentSelectorView::ContentSelector::ContentSelector(QWidget *parent) : + QObject(parent) { - assert(!mInstance); - mInstance = new ContentSelector (subject, flags); -} - -ContentSelectorView::ContentSelector& ContentSelectorView::ContentSelector::instance() -{ - - assert(mInstance); - return *mInstance; -} - -ContentSelectorView::ContentSelector::ContentSelector(QWidget *parent, unsigned char flags) : - QWidget(parent), mFlags (flags), - mAdjusterWidget (0), mFileWidget (0), - mIgnoreProfileSignal (false) -{ - - ui.setupUi (this); - - parent->setLayout(new QGridLayout()); - parent->layout()->addWidget(this); + ui.setupUi (parent); buildContentModel(); buildGameFileView(); buildAddonView(); - buildNewAddonView(); - buildLoadAddonView(); - buildProfilesView(); /* //mContentModel->sort(3); // Sort by date accessed @@ -57,16 +27,8 @@ ContentSelectorView::ContentSelector::ContentSelector(QWidget *parent, unsigned */ } -bool ContentSelectorView::ContentSelector::isFlagged(SelectorFlags flag) const -{ - return (mFlags & flag); -} - void ContentSelectorView::ContentSelector::buildContentModel() { - if (!isFlagged (Flag_Content)) - return; - mContentModel = new ContentSelectorModel::ContentModel(); if (mFilePaths.size()>0) @@ -83,12 +45,6 @@ void ContentSelectorView::ContentSelector::buildContentModel() void ContentSelectorView::ContentSelector::buildGameFileView() { - if (!isFlagged (Flag_Content)) - { - ui.gameFileView->setVisible(false); - return; - } - ui.gameFileView->setVisible (true); mGameFileProxyModel = new QSortFilterProxyModel(this); @@ -99,19 +55,17 @@ void ContentSelectorView::ContentSelector::buildGameFileView() ui.gameFileView->setPlaceholderText(QString("Select a game file...")); ui.gameFileView->setModel(mGameFileProxyModel); - connect (ui.gameFileView, SIGNAL(currentIndexChanged(int)), this, SLOT (slotCurrentGameFileIndexChanged(int))); + connect (ui.gameFileView, SIGNAL(currentIndexChanged(int)), + this, SLOT (slotCurrentGameFileIndexChanged(int))); + + connect (ui.gameFileView, SIGNAL (currentIndexChanged (int)), + this, SIGNAL (signalCurrentGamefileIndexChanged (int))); ui.gameFileView->setCurrentIndex(-1); } void ContentSelectorView::ContentSelector::buildAddonView() { - if (!isFlagged (Flag_Content)) - { - ui.addonView->setVisible(false); - return; - } - ui.addonView->setVisible (true); mAddonProxyModel = new QSortFilterProxyModel(this); @@ -123,91 +77,9 @@ void ContentSelectorView::ContentSelector::buildAddonView() ui.addonView->setModel(mAddonProxyModel); connect(ui.addonView, SIGNAL(clicked(const QModelIndex &)), this, SLOT(slotAddonTableItemClicked(const QModelIndex &))); -} -void ContentSelectorView::ContentSelector::buildProfilesView() -{ - if (!isFlagged (Flag_Profile)) - { - ui.profileGroupBox->setVisible(false); - return; - } - - ui.profileGroupBox->setVisible (true); - - // Add the actions to the toolbuttons - ui.newProfileButton->setDefaultAction (ui.newProfileAction); - ui.deleteProfileButton->setDefaultAction (ui.deleteProfileAction); - - //enable ui elements - ui.profilesComboBox->addItem ("Default"); - ui.profilesComboBox->setPlaceholderText (QString("Select a profile...")); - - if (!ui.profilesComboBox->isEnabled()) - ui.profilesComboBox->setEnabled(true); - - if (!ui.deleteProfileAction->isEnabled()) - ui.deleteProfileAction->setEnabled(true); - - //establish connections - connect (ui.profilesComboBox, SIGNAL (currentIndexChanged(int)), this, SLOT(slotCurrentProfileIndexChanged(int))); - connect (ui.profilesComboBox, SIGNAL (profileRenamed(QString,QString)), this, SIGNAL(signalProfileRenamed(QString,QString))); - connect (ui.profilesComboBox, SIGNAL (signalProfileChanged(QString,QString)), this, SIGNAL(signalProfileChangedByUser(QString,QString))); - connect (ui.profilesComboBox, SIGNAL (signalProfileTextChanged(QString)), this, SLOT (slotProfileTextChanged (QString))); - - ui.profileGroupBox->setVisible (true); - ui.projectButtonBox->setVisible (false); -} - -void ContentSelectorView::ContentSelector::buildLoadAddonView() -{ - if (!isFlagged (Flag_LoadAddon)) - { - if (!isFlagged (Flag_NewAddon)) - ui.projectGroupBox->setVisible (false); - - return; - } - - ui.projectGroupBox->setVisible (true); - ui.projectCreateButton->setVisible (false); - ui.projectGroupBox->setTitle (""); - - connect(ui.projectButtonBox, SIGNAL(accepted()), this, SIGNAL(accepted())); - connect(ui.projectButtonBox, SIGNAL(rejected()), this, SIGNAL(rejected())); -} - -void ContentSelectorView::ContentSelector::buildNewAddonView() -{ - if (!isFlagged (Flag_NewAddon)) - { - if (!isFlagged (Flag_LoadAddon)) - ui.projectGroupBox->setVisible (false); - - return; - } - - ui.projectGroupBox->setVisible (true); - - mFileWidget = new CSVDoc::FileWidget (this); - mAdjusterWidget = new CSVDoc::AdjusterWidget (this); - - mFileWidget->setType(true); - mFileWidget->extensionLabelIsVisible(false); - - ui.fileWidgetFrame->layout()->addWidget(mFileWidget); - ui.adjusterWidgetFrame->layout()->addWidget(mAdjusterWidget); - - ui.projectButtonBox->setStandardButtons(QDialogButtonBox::Cancel); - ui.projectButtonBox->addButton(ui.projectCreateButton, QDialogButtonBox::ActionRole); - - connect (mFileWidget, SIGNAL (nameChanged (const QString&, bool)), - mAdjusterWidget, SLOT (setName (const QString&, bool))); - - connect (mAdjusterWidget, SIGNAL (stateChanged(bool)), this, SLOT (slotUpdateCreateButton(bool))); - - connect(ui.projectCreateButton, SIGNAL(clicked()), this, SIGNAL(accepted())); - connect(ui.projectButtonBox, SIGNAL(rejected()), this, SIGNAL(rejected())); + for (int i = 0; i < mAddonProxyModel->rowCount(); ++i) + qDebug() << mAddonProxyModel->data(mAddonProxyModel->index(i,0,QModelIndex())); } void ContentSelectorView::ContentSelector::setGameFile(const QString &filename) @@ -239,16 +111,6 @@ void ContentSelectorView::ContentSelector::setCheckStates(const QStringList &lis mContentModel->setCheckState(file, Qt::Checked); } -QString ContentSelectorView::ContentSelector::projectFilename() const -{ - QString filepath = ""; - - if (mAdjusterWidget) - filepath = QString::fromAscii(mAdjusterWidget->getPath().c_str()); - - return filepath; -} - ContentSelectorModel::ContentFileList ContentSelectorView::ContentSelector::selectedFiles() const { @@ -261,65 +123,10 @@ ContentSelectorModel::ContentFileList void ContentSelectorView::ContentSelector::addFiles(const QString &path) { - // if the model hasn't been instantiated, queue the path - if (!mInstance) - mFilePaths.append(path); - else - { - mInstance->mContentModel->addFiles(path); - mInstance->ui.gameFileView->setCurrentIndex(-1); - } -} + mContentModel->addFiles(path); -int ContentSelectorView::ContentSelector::getProfileIndex ( const QString &item) const -{ - return ui.profilesComboBox->findText (item); -} - -void ContentSelectorView::ContentSelector::addProfile (const QString &item, bool setAsCurrent) -{ - if (item.isEmpty()) - return; - - QString previous = ui.profilesComboBox->currentText(); - - if (ui.profilesComboBox->findText(item) == -1) - ui.profilesComboBox->addItem(item); - - if (setAsCurrent) - setProfile (ui.profilesComboBox->findText(item)); -} - -void ContentSelectorView::ContentSelector::setProfile(int index) -{ - //programmatic change requires second call to non-signalized "slot" since no signal responses - //occur for programmatic changes to the profilesComboBox. - if (index >= -1 && index < ui.profilesComboBox->count()) - { - QString previous = ui.profilesComboBox->itemText(ui.profilesComboBox->currentIndex()); - QString current = ui.profilesComboBox->itemText(index); - - ui.profilesComboBox->setCurrentIndex(index); - } -} - -QString ContentSelectorView::ContentSelector::getProfileText() const -{ - return ui.profilesComboBox->currentText(); -} - -QAbstractItemModel *ContentSelectorView::ContentSelector::profilesModel() const -{ - return ui.profilesComboBox->model(); -} - -void ContentSelectorView::ContentSelector::slotCurrentProfileIndexChanged(int index) -{ - //don't allow deleting "Default" profile - bool success = (ui.profilesComboBox->itemText(index) != "Default"); - - ui.deleteProfileAction->setEnabled(success); - ui.profilesComboBox->setEditEnabled(success); + if (ui.gameFileView->currentIndex() != -1) + ui.gameFileView->setCurrentIndex(-1); } void ContentSelectorView::ContentSelector::slotCurrentGameFileIndexChanged(int index) @@ -341,16 +148,6 @@ void ContentSelectorView::ContentSelector::slotCurrentGameFileIndexChanged(int i if (proxy) proxy->setDynamicSortFilter(true); - - slotUpdateCreateButton(true); -} - -void ContentSelectorView::ContentSelector::slotProfileTextChanged(const QString &text) -{ - QPushButton *opnBtn = ui.projectButtonBox->button(QDialogButtonBox::Open); - - if (opnBtn->isEnabled()) - opnBtn->setEnabled (false); } void ContentSelectorView::ContentSelector::slotAddonTableItemClicked(const QModelIndex &index) @@ -362,53 +159,3 @@ void ContentSelectorView::ContentSelector::slotAddonTableItemClicked(const QMode else model->setData(index, Qt::Unchecked, Qt::CheckStateRole); } - -void ContentSelectorView::ContentSelector::slotUpdateCreateButton(bool) -{ - //enable only if a game file is selected and the adjuster widget is non-empty - bool validGameFile = (ui.gameFileView->currentIndex() != -1); - bool validFilename = false; - - if (mAdjusterWidget) - validFilename = mAdjusterWidget->isValid(); - - ui.projectCreateButton->setEnabled(validGameFile && validFilename); -} - -void ContentSelectorView::ContentSelector::on_newProfileAction_triggered() -{ - TextInputDialog newDialog (tr("New Profile"), tr("Profile name:"), this); - - if (newDialog.exec() == QDialog::Accepted) - emit signalAddNewProfile(newDialog.getText()); -} - -void ContentSelectorView::ContentSelector::on_deleteProfileAction_triggered() -{ - QString profile = ui.profilesComboBox->currentText(); - - if (profile.isEmpty()) - return; - - QMessageBox msgBox(this); - msgBox.setWindowTitle(tr("Delete Profile")); - msgBox.setIcon(QMessageBox::Warning); - msgBox.setStandardButtons(QMessageBox::Cancel); - msgBox.setText(tr("Are you sure you want to delete %0?").arg(profile)); - - QAbstractButton *deleteButton = - msgBox.addButton(tr("Delete"), QMessageBox::ActionRole); - - msgBox.exec(); - - if (msgBox.clickedButton() != deleteButton) - return; - - // Remove the profile from the combobox - ui.profilesComboBox->removeItem(ui.profilesComboBox->findText(profile)); - - //signal for removal from model - emit signalProfileDeleted (profile); - - slotCurrentProfileIndexChanged(ui.profilesComboBox->currentIndex()); -} diff --git a/components/contentselector/view/contentselector.hpp b/components/contentselector/view/contentselector.hpp index 64b9b37320..0882abfb77 100644 --- a/components/contentselector/view/contentselector.hpp +++ b/components/contentselector/view/contentselector.hpp @@ -3,38 +3,19 @@ #include -#include "ui_datafilespage.h" +#include "ui_contentselector.h" #include "../model/contentmodel.hpp" class QSortFilterProxyModel; -namespace CSVDoc -{ - class FileWidget; - class AdjusterWidget; -} namespace ContentSelectorView { - enum SelectorFlags - { - Flag_Content = 0x01, // gamefile combobox & addon list view - Flag_NewAddon = 0x02, // enable project button box (Create/Cancel) and file/adjuster widgets - Flag_LoadAddon = 0x04, // enable project button box (Open/Cancel) - Flag_Profile = 0x08 // enable profile combo box - }; - class ContentSelector : public QWidget + class ContentSelector : public QObject { Q_OBJECT - unsigned char mFlags; - bool mIgnoreProfileSignal; - - static ContentSelector *mInstance; - static QStringList mFilePaths; - - CSVDoc::FileWidget *mFileWidget; - CSVDoc::AdjusterWidget *mAdjusterWidget; + QStringList mFilePaths; protected: @@ -44,64 +25,39 @@ namespace ContentSelectorView public: - explicit ContentSelector(QWidget *parent = 0, unsigned char flags = Flag_Content); + explicit ContentSelector(QWidget *parent = 0); - static void configure(QWidget *subject, unsigned char flags = Flag_Content); - static ContentSelector &instance(); - static void addFiles(const QString &path); + void addFiles(const QString &path); void clearCheckStates(); void setCheckStates (const QStringList &list); - ContentSelectorModel::ContentFileList *CheckedItems(); - QString projectFilename() const; ContentSelectorModel::ContentFileList selectedFiles() const; - QAbstractItemModel *profilesModel() const; - void setGameFile (const QString &filename = ""); - void addProfile (const QString &item, bool setAsCurrent = false); - void setProfile (int index); - int getProfileIndex (const QString &item) const; - QString getProfileText() const; + void setGameFile (const QString &filename = QString("")); + + bool isGamefileSelected() const + { return ui.gameFileView->currentIndex() != -1; } + + QWidget *uiWidget() const + { return ui.contentGroupBox; } private: - Ui::DataFilesPage ui; + Ui::ContentSelector ui; void buildContentModel(); void buildGameFileView(); void buildAddonView(); - void buildProfilesView(); - void buildNewAddonView(); - void buildLoadAddonView(); - - bool isFlagged(SelectorFlags flag) const; signals: - - void accepted(); - void rejected(); - - void signalCreateButtonClicked(); - - void signalProfileRenamed(QString,QString); - void signalProfileChangedByUser(QString,QString); - void signalProfileDeleted(QString); - void signalAddNewProfile(QString); + void signalCurrentGamefileIndexChanged (int); private slots: - void slotProfileTextChanged (const QString &text); - void slotCurrentProfileIndexChanged(int index); void slotCurrentGameFileIndexChanged(int index); void slotAddonTableItemClicked(const QModelIndex &index); - - void slotUpdateCreateButton (bool); - - // Action slots - void on_newProfileAction_triggered(); - void on_deleteProfileAction_triggered(); }; } diff --git a/components/contentselector/view/lineedit.cpp b/components/contentselector/view/lineedit.cpp deleted file mode 100644 index b6fdfa805c..0000000000 --- a/components/contentselector/view/lineedit.cpp +++ /dev/null @@ -1,39 +0,0 @@ -#include -#include -#include - -#include "lineedit.hpp" - -ContentSelectorView::LineEdit::LineEdit(QWidget *parent) - : QLineEdit(parent) -{ - mClearButton = new QToolButton(this); - QPixmap pixmap(":images/clear.png"); - mClearButton->setIcon(QIcon(pixmap)); - mClearButton->setIconSize(pixmap.size()); - mClearButton->setCursor(Qt::ArrowCursor); - mClearButton->setStyleSheet("QToolButton { border: none; padding: 0px; }"); - mClearButton->hide(); - connect(mClearButton, SIGNAL(clicked()), this, SLOT(clear())); - connect(this, SIGNAL(textChanged(const QString&)), this, SLOT(updateClearButton(const QString&))); - int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); - - setObjectName(QString("LineEdit")); - setStyleSheet(QString("LineEdit { padding-right: %1px; } ").arg(mClearButton->sizeHint().width() + frameWidth + 1)); - QSize msz = minimumSizeHint(); - setMinimumSize(qMax(msz.width(), mClearButton->sizeHint().height() + frameWidth * 2 + 2), - qMax(msz.height(), mClearButton->sizeHint().height() + frameWidth * 2 + 2)); -} - -void ContentSelectorView::LineEdit::resizeEvent(QResizeEvent *) -{ - QSize sz = mClearButton->sizeHint(); - int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); - mClearButton->move(rect().right() - frameWidth - sz.width(), - (rect().bottom() + 1 - sz.height())/2); -} - -void ContentSelectorView::LineEdit::updateClearButton(const QString& text) -{ - mClearButton->setVisible(!text.isEmpty()); -} diff --git a/components/contentselector/view/profilescombobox.hpp b/components/contentselector/view/profilescombobox.hpp deleted file mode 100644 index fc87a94b40..0000000000 --- a/components/contentselector/view/profilescombobox.hpp +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef PROFILESCOMBOBOX_HPP -#define PROFILESCOMBOBOX_HPP - -#include -#include -class QString; -class QRegExpValidator; - -namespace ContentSelectorView -{ - class ProfilesComboBox : public QComboBox - { - Q_OBJECT - public: - explicit ProfilesComboBox(QWidget *parent = 0); - void setEditEnabled(bool editable); - void setPlaceholderText(const QString &text); - // void indexChanged(int index); - - signals: - void signalProfileTextChanged(const QString &item); - void signalProfileChanged(const QString &previous, const QString ¤t); - void signalProfileChanged(int index); - void profileRenamed(const QString &oldName, const QString &newName); - - private slots: - - void slotEditingFinished(); - void slotIndexChangedByUser(int index); - void slotTextChanged(const QString &text); - - private: - QString mOldProfile; - QString mPlaceholderText; - QRegExpValidator *mValidator; - - protected: - void paintEvent(QPaintEvent *); - }; -} - -#endif // PROFILESCOMBOBOX_HPP diff --git a/files/ui/contentselector.ui b/files/ui/contentselector.ui new file mode 100644 index 0000000000..b9b5ba5a03 --- /dev/null +++ b/files/ui/contentselector.ui @@ -0,0 +1,111 @@ + + + ContentSelector + + + + 0 + 0 + 518 + 436 + + + + + 0 + 0 + + + + Qt::DefaultContextMenu + + + + 0 + + + + + Content + + + + 3 + + + + + false + + + + + + + + 0 + 0 + + + + Qt::DefaultContextMenu + + + true + + + QAbstractItemView::NoEditTriggers + + + true + + + false + + + QAbstractItemView::DragDrop + + + Qt::MoveAction + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + Qt::ElideLeft + + + false + + + false + + + true + + + false + + + + + + + + + + + ContentSelectorView::ComboBox + QComboBox +
components/contentselector/view/combobox.hpp
+
+
+ + +
diff --git a/files/ui/datafilespage.ui b/files/ui/datafilespage.ui index 0cafd606a8..eb5ebc61d6 100644 --- a/files/ui/datafilespage.ui +++ b/files/ui/datafilespage.ui @@ -7,7 +7,7 @@ 0 0 518 - 436 + 108 @@ -21,188 +21,7 @@ - - - Content - - - - 3 - - - - - - - false - - - - - - - - - - - - 0 - 0 - - - - Qt::DefaultContextMenu - - - true - - - QAbstractItemView::NoEditTriggers - - - true - - - false - - - QAbstractItemView::DragDrop - - - Qt::MoveAction - - - true - - - QAbstractItemView::SingleSelection - - - QAbstractItemView::SelectRows - - - Qt::ElideLeft - - - false - - - false - - - true - - - false - - - - - - - - - - - - - - - 0 - 0 - - - - Project - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - 3 - - - 6 - - - 6 - - - 0 - - - - - - 0 - 0 - - - - QFrame::NoFrame - - - QFrame::Plain - - - 0 - - - - 0 - - - 0 - - - - - - - - false - - - - 0 - 0 - - - - Create - - - - - - - - - - - 1 - 0 - - - - QFrame::NoFrame - - - QFrame::Plain - - - 0 - - - - 0 - - - 0 - - - + @@ -232,9 +51,9 @@ 6 - + - false + true @@ -242,6 +61,9 @@ 0 + + Select a profiile + @@ -276,13 +98,6 @@
- - - - QDialogButtonBox::Cancel|QDialogButtonBox::Open - - -
@@ -332,9 +147,9 @@ - ContentSelectorView::ProfilesComboBox + ProfilesComboBox QComboBox -
components/contentselector/view/profilescombobox.hpp
+
apps/launcher/utils/profilescombobox.hpp
diff --git a/files/ui/filedialog.ui b/files/ui/filedialog.ui new file mode 100644 index 0000000000..114345e53b --- /dev/null +++ b/files/ui/filedialog.ui @@ -0,0 +1,73 @@ + + + FileDialog + + + + 0 + 0 + 518 + 108 + + + + + 0 + 0 + + + + Qt::DefaultContextMenu + + + + + + + + + Qt::NoFocus + + + Project Name + + + false + + + + 6 + + + 3 + + + 6 + + + 0 + + + 6 + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + + + + From ba3589bc763383ac03a2ebfb9710b20b86d178c8 Mon Sep 17 00:00:00 2001 From: graffy76 Date: Tue, 22 Oct 2013 22:20:21 -0500 Subject: [PATCH 107/434] Revert "Implemented ContentSelector as a singleton "charm" modifier for" This reverts commit 24b167b7552ce8bf6e62933a535752a899c77473. Conflicts: apps/launcher/datafilespage.cpp apps/opencs/editor.cpp apps/opencs/view/doc/filedialog.cpp apps/opencs/view/doc/filedialog.hpp components/contentselector/view/contentselector.cpp components/contentselector/view/contentselector.hpp --- apps/launcher/datafilespage.cpp | 1 - apps/opencs/editor.cpp | 9 +++-- apps/opencs/view/doc/filedialog.cpp | 6 ++-- apps/opencs/view/doc/filedialog.hpp | 7 ++-- .../contentselector/view/contentselector.cpp | 33 +++++++------------ .../contentselector/view/contentselector.hpp | 1 - 6 files changed, 23 insertions(+), 34 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 2140caaf91..ca404c5d8b 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -28,7 +28,6 @@ DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gam , QWidget(parent) { ui.setupUi (this); - setObjectName ("DataFilesPage"); mSelector = new ContentSelectorView::ContentSelector (ui.contentSelectorWidget); diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index e78357bc54..284762812c 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -121,6 +121,7 @@ void CS::Editor::loadDocument() void CS::Editor::openFiles() { std::vector files; + QStringList paths = mFileDialog.checkedItemsPaths(); foreach (const QString &path, mFileDialog.selectedFilePaths()) { files.push_back(path.toStdString()); @@ -130,28 +131,30 @@ void CS::Editor::openFiles() qDebug() << "loading files: " << fp.c_str(); /// \todo Get the save path from the file dialogue + CSMDoc::Document *document = mDocumentManager.addDocument (files, *files.rbegin(), false); mViewManager.addView (document); - mFileDialog.close(); + mFileDialog.hide(); } void CS::Editor::createNewFile() { std::vector files; + QStringList paths = mFileDialog.checkedItemsPaths(); foreach (const QString &path, mFileDialog.selectedFilePaths()) { files.push_back(path.toStdString()); } - files.push_back(mFileDialog.filename().toStdString()); + files.push_back(mFileDialog.fileName().toStdString()); /// \todo Get the save path from the file dialogue. CSMDoc::Document *document = mDocumentManager.addDocument (files, *files.rbegin(), true); mViewManager.addView (document); - mFileDialog.close(); + mFileDialog.hide(); } void CS::Editor::createNewGame (const boost::filesystem::path& file) diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index 49e90ec813..5438effffd 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -10,10 +10,10 @@ #include #include #include -#include #include "components/contentselector/model/esmfile.hpp" #include "components/contentselector/view/contentselector.hpp" + #include "filewidget.hpp" #include "adjusterwidget.hpp" @@ -101,9 +101,9 @@ void CSVDoc::FileDialog::buildOpenFileView() connect (ui.projectButtonBox, SIGNAL (rejected()), this , SIGNAL (rejected())); } -void CSVDoc::FileDialog::slotRejected() +void CSVDoc::FileDialog::slotGameFileSelected(int value) { - close(); + emit signalUpdateCreateButton(value > -1, 1); } void CSVDoc::FileDialog::slotUpdateCreateButton (int) diff --git a/apps/opencs/view/doc/filedialog.hpp b/apps/opencs/view/doc/filedialog.hpp index 78883791e4..85582e95f4 100644 --- a/apps/opencs/view/doc/filedialog.hpp +++ b/apps/opencs/view/doc/filedialog.hpp @@ -53,19 +53,18 @@ namespace CSVDoc void buildOpenFileView(); signals: - void openFiles(); void createNewFile(); - public slots: + void signalUpdateCreateButton (bool, int); + void signalUpdateCreateButtonFlags(int); - void slotRejected(); + public slots: private slots: void slotUpdateCreateButton (int); void slotUpdateCreateButton (const QString &, bool); - }; } #endif // FILEDIALOG_HPP diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index 7a7e1fd9d5..cb405f092a 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -6,6 +6,7 @@ #include #include + #include #include #include @@ -21,26 +22,13 @@ ContentSelectorView::ContentSelector::ContentSelector(QWidget *parent) : buildGameFileView(); buildAddonView(); - /* - //mContentModel->sort(3); // Sort by date accessed - -*/ + updateViews(); } void ContentSelectorView::ContentSelector::buildContentModel() { mContentModel = new ContentSelectorModel::ContentModel(); - - if (mFilePaths.size()>0) - { - foreach (const QString &path, mFilePaths) - mContentModel->addFiles(path); - - mFilePaths.clear(); - } - - ui.gameFileView->setCurrentIndex(-1); - mContentModel->uncheckAll(); + connect(mContentModel, SIGNAL(layoutChanged()), this, SLOT(updateViews())); } void ContentSelectorView::ContentSelector::buildGameFileView() @@ -52,8 +40,8 @@ void ContentSelectorView::ContentSelector::buildGameFileView() mGameFileProxyModel->setFilterRole (Qt::UserRole); mGameFileProxyModel->setSourceModel (mContentModel); - ui.gameFileView->setPlaceholderText(QString("Select a game file...")); - ui.gameFileView->setModel(mGameFileProxyModel); + gameFileView->setPlaceholderText(QString("Select a game file...")); + gameFileView->setModel(mGameFileProxyModel); connect (ui.gameFileView, SIGNAL(currentIndexChanged(int)), this, SLOT (slotCurrentGameFileIndexChanged(int))); @@ -61,7 +49,8 @@ void ContentSelectorView::ContentSelector::buildGameFileView() connect (ui.gameFileView, SIGNAL (currentIndexChanged (int)), this, SIGNAL (signalCurrentGamefileIndexChanged (int))); - ui.gameFileView->setCurrentIndex(-1); + gameFileView->setCurrentIndex(-1); + gameFileView->setCurrentIndex(0); } void ContentSelectorView::ContentSelector::buildAddonView() @@ -74,7 +63,7 @@ void ContentSelectorView::ContentSelector::buildAddonView() mAddonProxyModel->setDynamicSortFilter (true); mAddonProxyModel->setSourceModel (mContentModel); - ui.addonView->setModel(mAddonProxyModel); + addonView->setModel(mAddonProxyModel); connect(ui.addonView, SIGNAL(clicked(const QModelIndex &)), this, SLOT(slotAddonTableItemClicked(const QModelIndex &))); @@ -120,7 +109,6 @@ ContentSelectorModel::ContentFileList return mContentModel->checkedItems(); } - void ContentSelectorView::ContentSelector::addFiles(const QString &path) { mContentModel->addFiles(path); @@ -133,7 +121,7 @@ void ContentSelectorView::ContentSelector::slotCurrentGameFileIndexChanged(int i { static int oldIndex = -1; - QAbstractItemModel *const model = ui.gameFileView->model(); + QAbstractItemModel *const model = gameFileView->model(); QSortFilterProxyModel *proxy = dynamic_cast(model); if (proxy) @@ -152,7 +140,8 @@ void ContentSelectorView::ContentSelector::slotCurrentGameFileIndexChanged(int i void ContentSelectorView::ContentSelector::slotAddonTableItemClicked(const QModelIndex &index) { - QAbstractItemModel *const model = ui.addonView->model(); + QAbstractItemModel *const model = addonView->model(); + //QSortFilterProxyModel *proxy = dynamic_cast(model); if (model->data(index, Qt::CheckStateRole).toInt() == Qt::Unchecked) model->setData(index, Qt::Checked, Qt::CheckStateRole); diff --git a/components/contentselector/view/contentselector.hpp b/components/contentselector/view/contentselector.hpp index 0882abfb77..163b19855d 100644 --- a/components/contentselector/view/contentselector.hpp +++ b/components/contentselector/view/contentselector.hpp @@ -10,7 +10,6 @@ class QSortFilterProxyModel; namespace ContentSelectorView { - class ContentSelector : public QObject { Q_OBJECT From e34cbe857c7735c4a39dd3be12fd22ea94af2939 Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Wed, 23 Oct 2013 12:37:28 +0200 Subject: [PATCH 108/434] Added a little more to the filters. --- manual/opencs/filters.tex | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/manual/opencs/filters.tex b/manual/opencs/filters.tex index 5a9ca6d9ce..d6ca6eb858 100644 --- a/manual/opencs/filters.tex +++ b/manual/opencs/filters.tex @@ -2,12 +2,41 @@ \subsection{Introduction} Filters are the key element of OpenCS use cases by allowing rapid and easy access to the seeked records presented in all tables. Therefore: in order to use this application fully effective you should make sure that all concepts and instructions written in the this section of the manual are perfectly clear to you.\\ Don't be afraid though, filters are fairly intuitive and easy to use. + \subsection{Used Terms} + \begin{description} \item[Filter] is generally speaking a tool able to ``Filter'' (that is: select some elements, while discarding others) according to the some criteria. In case of OpenCS: records are being filtred according to the criteria of user choice. Criteria are written down in language with simple syntax. \item[Criteria] describes condition under with any any record is being select by the filter. \item[Syntax] as you may noticed computers (in general) are rather strict, and expect only strictly formulated orders -- that is: written with correct syntax. Our syntax is simple and described in the {B}asics subsection. \item[Expression] is a criteria, only written with OpenCS filter syntax. + \item[Tokken] is any part of the expression, responsible for checking for the criteria in specified column. + \item[Node] is any part of the expression, responsible for performing logical operations on tokkens. That is: group two (or more) tokkens together in order to create a expression that will check for criteria placed in two (again: or more) columns (logical ``or'', ``and''); create any expression that will show only records that does not met criteria of specific tokken(logical ``not''). \end{description} + \subsection{Basics} -\subsection{Advanced Usage} \ No newline at end of file +To summarize and lay the very fundaments of this chapter: if you want to display filters of your choice (and you really do, because that's the use case on which opencs is designed) you have to know exactly what do you want to get in order to translate this into the tokkens and nodes. Finally, you will have to write this as legal expression -- that is: using correct syntax. As a result table will show only desired rows. + +\subsection{Interface} + +\subsection{Using predefined filters} + +\subsection{Filter scopes} + +\subsubsection{Tokkens} +Each tokken is used in similar manner. First off: you have to write it's name (for instance: ``string'') and secondly: condition that will be checked inside brackets (for instance string(something, something)). If conditions of your expression will be meet by a record (technical speaking: evaluated true) the record will show up in the table. +\linebreak +It is clear that you need to know what are you checking, that's is: what column of the table contains information that you are interested in and what should be inside specific cell inside this column to meet your requirements. In most cases first word inside brackets sets column you want to see, while the second one sets desired value inside of the cell. To separate column from the value use comma. + +\paragraph{String -- string(``column'', ``value'')} +String in programmers language is often just a word for anything composed of characters. In case of OpenCS this is in fact every value inside the column that is not composed of the pure numbers. Even columns containing only ``true`` and ``false`` values can be targeted by the string tokken.\footnote{There is no Boolean (''true'' or ``false'') value in the OpenCS. You should use string tokken for those.} String evaluates to true, when record contains in the specified column exactly the same value as specified.\footnote{This is not completely valid, however at this point this approach can be useful.} +\linebreak +Since majority of the columns contain string values, string tokken is among the most often used. Examples: +\begin{itemize} + \item string(``Record Type'', ``Weapon'') -- will evaluate to true for all records containing ``Weapon'' in the ``Record Type'' column cell. This group contains every weapon (including arrows and bolts) found in the game. + \item string(``Portable'', ``true'') -- will evaluate to true for all records containing word true inside ``Portable'' column cell. This group contains every portable light sources (lanterns, torches etc.). +\end{itemize} +String tokken can also use regular expressions (regexps) as it's value. This will be described in the ``Advanced'' section. + +\paragraph{Value -- value(``value'', (``open'', ``close''))} +While string tokken covers vast group of columns containing string values, there are in fact columns with just numerical values like ``weight`` \ No newline at end of file From 11779c27d308063274ffd30d9ac9920878c5f5a8 Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Wed, 23 Oct 2013 21:34:59 +0200 Subject: [PATCH 109/434] =?UTF-8?q?Writting=20that=20manual=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- manual/opencs/filters.tex | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/manual/opencs/filters.tex b/manual/opencs/filters.tex index d6ca6eb858..be406b3c06 100644 --- a/manual/opencs/filters.tex +++ b/manual/opencs/filters.tex @@ -18,10 +18,38 @@ Don't be afraid though, filters are fairly intuitive and easy to use. To summarize and lay the very fundaments of this chapter: if you want to display filters of your choice (and you really do, because that's the use case on which opencs is designed) you have to know exactly what do you want to get in order to translate this into the tokkens and nodes. Finally, you will have to write this as legal expression -- that is: using correct syntax. As a result table will show only desired rows. \subsection{Interface} +Above each table there is a field that is used to enter filter: either predefined by the OpenMW developers or made by you, the user. You probabbly noticed it before. However there is alo completely new element, although using familiar table layout. Go to the application menu view, and click filters. You should see set of default filters, made by the OpenMW team in the table with the following columns: filter, description and modyfied. +\begin{description} + \item[Filter] column containing expression of the filter. + \item[Description] contains the short description of the filter function. + \item[Name] constains the name of the filter. + \item[Modyfied] just like in all other tables you have seen so far modyfied indicates if a filter was added, modyfied or removed. +\end{description} + +So let's learn how to actually use those to speed up your work. \subsection{Using predefined filters} +Using those filters is quite easy and involves typing inside the filter field above the table. For instance, try to open referencables table and type in the filters field the following: ``project::weapons''. As soon as you complete the text table will magicly alters and will show only the weapons. As you could noticed project::weapons is nothing else than a name of one of the predefined filters. That's it: in order to use the filter inside the table you simply type it's name inside the filter field. +\linebreak +To make life easier filter names follow simple convention. + +\begin{itemize} + \item Filter name filtring a specific record type contains usually the name of specific group. For instance project::weapons filter contains the word weapons (did you noticed?). Plural form is always used. + \item When filtering specific subgroup the name starts just like in the case of general filter. For instance project::weaponssilver will filter only silver weapons (new mechanic introduced by the Bloodmoon, silver weapons deal double damage against werewolfs) and project::weaponsmagical will filter only magical weapons (able to hurt ghosts and other supernatural creatures). + \item There are few exceptions from the above. For instance there is a project::added, project::removed, project::modyfied, project::base. You could probabbly except something more like ``project::statusadded'' but in this case typing this few extra characters would only help to break your keyboard faster. +\end{itemize} + +I strongly recommend to take a look at the filters table right now to see what you can filter with that. And try using it! It is very simple. \subsection{Filter scopes} +Back to the manual? Good. Now let's explain the cryptic project:: at the begining of every predefined filter. It is a scope. Scope determinates if the filter will be stored along with your project or if it will be forgotten as soon as OpenCS quits. +\begin{description} + \item[project::] scope indicates that filter is stored inside the project file. + \item[session::] scope indicates that filter is not stored inside the project file, and once you will quit OpenCS (close session) the filter will be gone. Forever! Untill then it can be found inside the filters table. +\end{description} +In addition to this two scopes, there is a third one; called one-shot. One-shot filters are not stored anywhere and as the name implies they are supposed to be created when needed just once. Good thing about the one-shot filters is that you don't need to open filters table in order to create it. Instead you type it directly inside the filter field, starting with ``!''. +\linebreak +Still, you may wonder how you are supposed to write expressions, what and nodes tokkens are avaible, and what syntax looks like. \subsubsection{Tokkens} Each tokken is used in similar manner. First off: you have to write it's name (for instance: ``string'') and secondly: condition that will be checked inside brackets (for instance string(something, something)). If conditions of your expression will be meet by a record (technical speaking: evaluated true) the record will show up in the table. From b48f066f33e0d54b5369e13194175ea2736203c7 Mon Sep 17 00:00:00 2001 From: graffy76 Date: Wed, 23 Oct 2013 17:39:17 -0500 Subject: [PATCH 110/434] Reimplemented content selector for filedialog and datafilespage classes --- apps/opencs/editor.cpp | 4 +--- apps/opencs/view/doc/filedialog.cpp | 13 ++++++------- apps/opencs/view/doc/filedialog.hpp | 4 ++-- .../contentselector/view/contentselector.cpp | 18 ++++++++---------- 4 files changed, 17 insertions(+), 22 deletions(-) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 284762812c..1099226d2c 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -121,7 +121,6 @@ void CS::Editor::loadDocument() void CS::Editor::openFiles() { std::vector files; - QStringList paths = mFileDialog.checkedItemsPaths(); foreach (const QString &path, mFileDialog.selectedFilePaths()) { files.push_back(path.toStdString()); @@ -141,13 +140,12 @@ void CS::Editor::openFiles() void CS::Editor::createNewFile() { std::vector files; - QStringList paths = mFileDialog.checkedItemsPaths(); foreach (const QString &path, mFileDialog.selectedFilePaths()) { files.push_back(path.toStdString()); } - files.push_back(mFileDialog.fileName().toStdString()); + files.push_back(mFileDialog.filename().toStdString()); /// \todo Get the save path from the file dialogue. diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index 5438effffd..1ac476cb29 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -88,7 +88,6 @@ void CSVDoc::FileDialog::buildNewFileView() this, SLOT (slotUpdateCreateButton (int))); connect (ui.projectButtonBox, SIGNAL (accepted()), this, SIGNAL (createNewFile())); - connect (ui.projectButtonBox, SIGNAL (rejected()), this, SLOT (slotRejected())); } @@ -98,12 +97,7 @@ void CSVDoc::FileDialog::buildOpenFileView() ui.projectGroupBox->setTitle (QString("")); connect (ui.projectButtonBox, SIGNAL (accepted()), this, SIGNAL (openFiles())); - connect (ui.projectButtonBox, SIGNAL (rejected()), this , SIGNAL (rejected())); -} - -void CSVDoc::FileDialog::slotGameFileSelected(int value) -{ - emit signalUpdateCreateButton(value > -1, 1); + connect (ui.projectButtonBox, SIGNAL (rejected()), this, SLOT (slotRejected())); } void CSVDoc::FileDialog::slotUpdateCreateButton (int) @@ -129,3 +123,8 @@ QString CSVDoc::FileDialog::filename() const return QString (""); } +void CSVDoc::FileDialog::slotRejected() +{ + emit rejected(); + close(); +} diff --git a/apps/opencs/view/doc/filedialog.hpp b/apps/opencs/view/doc/filedialog.hpp index 85582e95f4..3b93f42a86 100644 --- a/apps/opencs/view/doc/filedialog.hpp +++ b/apps/opencs/view/doc/filedialog.hpp @@ -53,18 +53,18 @@ namespace CSVDoc void buildOpenFileView(); signals: + void openFiles(); void createNewFile(); void signalUpdateCreateButton (bool, int); void signalUpdateCreateButtonFlags(int); - public slots: - private slots: void slotUpdateCreateButton (int); void slotUpdateCreateButton (const QString &, bool); + void slotRejected(); }; } #endif // FILEDIALOG_HPP diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index cb405f092a..3d615906d8 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -21,14 +21,12 @@ ContentSelectorView::ContentSelector::ContentSelector(QWidget *parent) : buildContentModel(); buildGameFileView(); buildAddonView(); - - updateViews(); } void ContentSelectorView::ContentSelector::buildContentModel() { mContentModel = new ContentSelectorModel::ContentModel(); - connect(mContentModel, SIGNAL(layoutChanged()), this, SLOT(updateViews())); + //connect(mContentModel, SIGNAL(layoutChanged()), this, SLOT(updateViews())); } void ContentSelectorView::ContentSelector::buildGameFileView() @@ -40,8 +38,8 @@ void ContentSelectorView::ContentSelector::buildGameFileView() mGameFileProxyModel->setFilterRole (Qt::UserRole); mGameFileProxyModel->setSourceModel (mContentModel); - gameFileView->setPlaceholderText(QString("Select a game file...")); - gameFileView->setModel(mGameFileProxyModel); + ui.gameFileView->setPlaceholderText(QString("Select a game file...")); + ui.gameFileView->setModel(mGameFileProxyModel); connect (ui.gameFileView, SIGNAL(currentIndexChanged(int)), this, SLOT (slotCurrentGameFileIndexChanged(int))); @@ -49,8 +47,8 @@ void ContentSelectorView::ContentSelector::buildGameFileView() connect (ui.gameFileView, SIGNAL (currentIndexChanged (int)), this, SIGNAL (signalCurrentGamefileIndexChanged (int))); - gameFileView->setCurrentIndex(-1); - gameFileView->setCurrentIndex(0); + ui.gameFileView->setCurrentIndex(-1); + ui.gameFileView->setCurrentIndex(0); } void ContentSelectorView::ContentSelector::buildAddonView() @@ -63,7 +61,7 @@ void ContentSelectorView::ContentSelector::buildAddonView() mAddonProxyModel->setDynamicSortFilter (true); mAddonProxyModel->setSourceModel (mContentModel); - addonView->setModel(mAddonProxyModel); + ui.addonView->setModel(mAddonProxyModel); connect(ui.addonView, SIGNAL(clicked(const QModelIndex &)), this, SLOT(slotAddonTableItemClicked(const QModelIndex &))); @@ -121,7 +119,7 @@ void ContentSelectorView::ContentSelector::slotCurrentGameFileIndexChanged(int i { static int oldIndex = -1; - QAbstractItemModel *const model = gameFileView->model(); + QAbstractItemModel *const model = ui.gameFileView->model(); QSortFilterProxyModel *proxy = dynamic_cast(model); if (proxy) @@ -140,7 +138,7 @@ void ContentSelectorView::ContentSelector::slotCurrentGameFileIndexChanged(int i void ContentSelectorView::ContentSelector::slotAddonTableItemClicked(const QModelIndex &index) { - QAbstractItemModel *const model = addonView->model(); + QAbstractItemModel *const model = ui.addonView->model(); //QSortFilterProxyModel *proxy = dynamic_cast(model); if (model->data(index, Qt::CheckStateRole).toInt() == Qt::Unchecked) From e89f1dd40a1f54a921037dc7e54970c909984b25 Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Thu, 24 Oct 2013 18:33:35 +0200 Subject: [PATCH 111/434] Corrected, according to Zini. Not completly, though. --- manual/opencs/filters.tex | 54 +++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/manual/opencs/filters.tex b/manual/opencs/filters.tex index be406b3c06..d6fafc4aaf 100644 --- a/manual/opencs/filters.tex +++ b/manual/opencs/filters.tex @@ -1,70 +1,70 @@ \section{Filters} \subsection{Introduction} -Filters are the key element of OpenCS use cases by allowing rapid and easy access to the seeked records presented in all tables. Therefore: in order to use this application fully effective you should make sure that all concepts and instructions written in the this section of the manual are perfectly clear to you.\\ +Filters are the key element of OpenCS use cases by allowing rapid and easy access to the searched records presented in all tables. Therefore: in order to use this application fully effective you should make sure that all concepts and instructions written in the this section of the manual are perfectly clear to you.\\ Don't be afraid though, filters are fairly intuitive and easy to use. \subsection{Used Terms} \begin{description} - \item[Filter] is generally speaking a tool able to ``Filter'' (that is: select some elements, while discarding others) according to the some criteria. In case of OpenCS: records are being filtred according to the criteria of user choice. Criteria are written down in language with simple syntax. + \item[Filter] is generally speaking a tool able to ``Filter'' (that is: select some elements, while discarding others) according to the some criteria. In case of OpenCS: records are being filtered according to the criteria of user choice. Criteria are written down in language with simple syntax. \item[Criteria] describes condition under with any any record is being select by the filter. \item[Syntax] as you may noticed computers (in general) are rather strict, and expect only strictly formulated orders -- that is: written with correct syntax. Our syntax is simple and described in the {B}asics subsection. \item[Expression] is a criteria, only written with OpenCS filter syntax. - \item[Tokken] is any part of the expression, responsible for checking for the criteria in specified column. - \item[Node] is any part of the expression, responsible for performing logical operations on tokkens. That is: group two (or more) tokkens together in order to create a expression that will check for criteria placed in two (again: or more) columns (logical ``or'', ``and''); create any expression that will show only records that does not met criteria of specific tokken(logical ``not''). + \item[Token] is any part of the expression, responsible for checking for the criteria in specified column. + \item[Node] is any part of the expression, responsible for performing logical operations on tokens. That is: group two (or more) tokens together in order to create a expression that will check for criteria placed in two (again: or more) columns (logical ``or'', ``and''); create any expression that will show only records that does not met criteria of specific token(logical ``not''). \end{description} \subsection{Basics} -To summarize and lay the very fundaments of this chapter: if you want to display filters of your choice (and you really do, because that's the use case on which opencs is designed) you have to know exactly what do you want to get in order to translate this into the tokkens and nodes. Finally, you will have to write this as legal expression -- that is: using correct syntax. As a result table will show only desired rows. +In fact you don't need to learn everything about filters in order to use them. In fact all you need to know to achieve decent productivity with OpenCS is inside basics section. \subsection{Interface} -Above each table there is a field that is used to enter filter: either predefined by the OpenMW developers or made by you, the user. You probabbly noticed it before. However there is alo completely new element, although using familiar table layout. Go to the application menu view, and click filters. You should see set of default filters, made by the OpenMW team in the table with the following columns: filter, description and modyfied. +Above each table there is a field that is used to enter filter: either predefined by the OpenMW developers or made by you, the user. You probably noticed it before. However there is also completely new element, although using familiar table layout. Go to the application menu view, and click filters. You should see set of default filters, made by the OpenMW team in the table with the following columns: filter, description and modified. \begin{description} + \item[ID] contains the name of the filter. + \item[Modified] just like in all other tables you have seen so far modified indicates if a filter was added, modified or removed. \item[Filter] column containing expression of the filter. \item[Description] contains the short description of the filter function. - \item[Name] constains the name of the filter. - \item[Modyfied] just like in all other tables you have seen so far modyfied indicates if a filter was added, modyfied or removed. \end{description} So let's learn how to actually use those to speed up your work. \subsection{Using predefined filters} -Using those filters is quite easy and involves typing inside the filter field above the table. For instance, try to open referencables table and type in the filters field the following: ``project::weapons''. As soon as you complete the text table will magicly alters and will show only the weapons. As you could noticed project::weapons is nothing else than a name of one of the predefined filters. That's it: in order to use the filter inside the table you simply type it's name inside the filter field. -\linebreak +Using those filters is quite easy and involves typing inside the filter field above the table. For instance, try to open referencables table and type in the filters field the following: ``project::weapons''. As soon as you complete the text table will magicly alters and will show only the weapons. As you could noticed project::weapons is nothing else than a name of one of the predefined filters. That's it: in order to use the filter inside the table you simply type it's name inside the filter field.\\ To make life easier filter names follow simple convention. \begin{itemize} - \item Filter name filtring a specific record type contains usually the name of specific group. For instance project::weapons filter contains the word weapons (did you noticed?). Plural form is always used. + \item Filter name filtering a specific record type contains usually the name of specific group. For instance project::weapons filter contains the word weapons (did you noticed?). Plural form is always used. \item When filtering specific subgroup the name starts just like in the case of general filter. For instance project::weaponssilver will filter only silver weapons (new mechanic introduced by the Bloodmoon, silver weapons deal double damage against werewolfs) and project::weaponsmagical will filter only magical weapons (able to hurt ghosts and other supernatural creatures). - \item There are few exceptions from the above. For instance there is a project::added, project::removed, project::modyfied, project::base. You could probabbly except something more like ``project::statusadded'' but in this case typing this few extra characters would only help to break your keyboard faster. + \item There are few exceptions from the above. For instance there is a project::added, project::removed, project::modyfied, project::base. You would probably except something more like ``project::statusadded'' but in this case typing this few extra characters would only help to break your keyboard faster. \end{itemize} -I strongly recommend to take a look at the filters table right now to see what you can filter with that. And try using it! It is very simple. +We strongly recommend to take a look at the filters table right now to see what you can filter with that. And try using it! It is very simple. -\subsection{Filter scopes} -Back to the manual? Good. Now let's explain the cryptic project:: at the begining of every predefined filter. It is a scope. Scope determinates if the filter will be stored along with your project or if it will be forgotten as soon as OpenCS quits. +\subsection{Advanced} +If you want to create your own filter you have to know exactly what do you want to get in order to translate this into the tokens and nodes. Finally, you will have to write this as legal expression -- that is: using correct syntax. As a result table will show only desired rows.\\ +Advance subsection covers everything that you need to know in order to create any filter you may want to. +\subsection{Namespaces} +It is a ``namespace``, a term borrowed from the C++ language. In case of OpenCS namespace determinate if the filter will be stored along with your project or if it will be forgotten as soon as OpenCS quits. \begin{description} - \item[project::] scope indicates that filter is stored inside the project file. - \item[session::] scope indicates that filter is not stored inside the project file, and once you will quit OpenCS (close session) the filter will be gone. Forever! Untill then it can be found inside the filters table. + \item[project::] namespace indicates that filter is stored inside the project file. + \item[session::] namespace indicates that filter is not stored inside the project file, and once you will quit OpenCS (close session) the filter will be gone. Forever! Until then it can be found inside the filters table. \end{description} -In addition to this two scopes, there is a third one; called one-shot. One-shot filters are not stored anywhere and as the name implies they are supposed to be created when needed just once. Good thing about the one-shot filters is that you don't need to open filters table in order to create it. Instead you type it directly inside the filter field, starting with ``!''. -\linebreak -Still, you may wonder how you are supposed to write expressions, what and nodes tokkens are avaible, and what syntax looks like. +In addition to this two scopes, there is a third one; called one-shot. One-shot filters are not stored anywhere and as the name implies they are supposed to be created when needed only once. Good thing about the one-shot filters is that you don't need to open filters table in order to create it. Instead you just type it directly inside the filter field, starting with ``!''.\\ +Still, you may wonder how you are supposed to write expressions, what and nodes tokens are avaible, and what syntax looks like. -\subsubsection{Tokkens} -Each tokken is used in similar manner. First off: you have to write it's name (for instance: ``string'') and secondly: condition that will be checked inside brackets (for instance string(something, something)). If conditions of your expression will be meet by a record (technical speaking: evaluated true) the record will show up in the table. -\linebreak +\subsubsection{Tokens} +Each token is used in similar manner. First off: you have to write it's name (for instance: ``string'') and secondly: condition that will be checked inside brackets (for instance string(something, something)). If conditions of your expression will be meet by a record (technical speaking: evaluated true) the record will show up in the table.\\ It is clear that you need to know what are you checking, that's is: what column of the table contains information that you are interested in and what should be inside specific cell inside this column to meet your requirements. In most cases first word inside brackets sets column you want to see, while the second one sets desired value inside of the cell. To separate column from the value use comma. \paragraph{String -- string(``column'', ``value'')} -String in programmers language is often just a word for anything composed of characters. In case of OpenCS this is in fact every value inside the column that is not composed of the pure numbers. Even columns containing only ``true`` and ``false`` values can be targeted by the string tokken.\footnote{There is no Boolean (''true'' or ``false'') value in the OpenCS. You should use string tokken for those.} String evaluates to true, when record contains in the specified column exactly the same value as specified.\footnote{This is not completely valid, however at this point this approach can be useful.} +String in programmers language is often just a word for anything composed of characters. In case of OpenCS this is in fact true for every value inside the column that is not composed of the pure numbers. Even columns containing only ``true`` and ``false`` values can be targeted by the string token.\footnote{There is no Boolean (''true'' or ``false'') value in the OpenCS. You should use string token for those.} String evaluates to true, when record contains in the specified column exactly the same value as specified.\footnote{This is not completely valid, however at this point this approach can be useful.} \linebreak -Since majority of the columns contain string values, string tokken is among the most often used. Examples: +Since majority of the columns contain string values, string token is among the most often used. Examples: \begin{itemize} \item string(``Record Type'', ``Weapon'') -- will evaluate to true for all records containing ``Weapon'' in the ``Record Type'' column cell. This group contains every weapon (including arrows and bolts) found in the game. \item string(``Portable'', ``true'') -- will evaluate to true for all records containing word true inside ``Portable'' column cell. This group contains every portable light sources (lanterns, torches etc.). \end{itemize} -String tokken can also use regular expressions (regexps) as it's value. This will be described in the ``Advanced'' section. +String token can also use regular expressions (regexps) as it's value. This will be described in the ``Advanced'' section. \paragraph{Value -- value(``value'', (``open'', ``close''))} -While string tokken covers vast group of columns containing string values, there are in fact columns with just numerical values like ``weight`` \ No newline at end of file +While string token covers vast group of columns containing string values, there are in fact columns with just numerical values like ``weight``. To filter those we need a value token. This one works in similar manner to the string filter: first token name and criteria inside brackets. Clearly, conditions should hold column to test in. However in this case wanted value is specified as a range. \ No newline at end of file From cef3b30b25cdb48f6004dd68e12d3b58c90999ea Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Thu, 24 Oct 2013 19:24:37 +0200 Subject: [PATCH 112/434] Added tables.tex. It is almost empty at the moment. --- manual/opencs/filters.tex | 10 +++++++--- manual/opencs/tables.tex | 10 ++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 manual/opencs/tables.tex diff --git a/manual/opencs/filters.tex b/manual/opencs/filters.tex index d6fafc4aaf..1863987dcb 100644 --- a/manual/opencs/filters.tex +++ b/manual/opencs/filters.tex @@ -57,14 +57,18 @@ Each token is used in similar manner. First off: you have to write it's name (fo It is clear that you need to know what are you checking, that's is: what column of the table contains information that you are interested in and what should be inside specific cell inside this column to meet your requirements. In most cases first word inside brackets sets column you want to see, while the second one sets desired value inside of the cell. To separate column from the value use comma. \paragraph{String -- string(``column'', ``value'')} -String in programmers language is often just a word for anything composed of characters. In case of OpenCS this is in fact true for every value inside the column that is not composed of the pure numbers. Even columns containing only ``true`` and ``false`` values can be targeted by the string token.\footnote{There is no Boolean (''true'' or ``false'') value in the OpenCS. You should use string token for those.} String evaluates to true, when record contains in the specified column exactly the same value as specified.\footnote{This is not completely valid, however at this point this approach can be useful.} -\linebreak +String in programmers language is often\footnote{Often, not always. There are different programming languages using slightly different terms.} just a word for anything composed of characters. In case of OpenCS this is in fact true for every value inside the column that is not composed of the pure numbers. Even columns containing only ``true`` and ``false`` values can be targeted by the string token.\footnote{There is no Boolean (''true'' or ``false'') value in the OpenCS. You should use string token for those.} String evaluates to true, when record contains in the specified column exactly the same value as specified. +\\ Since majority of the columns contain string values, string token is among the most often used. Examples: \begin{itemize} \item string(``Record Type'', ``Weapon'') -- will evaluate to true for all records containing ``Weapon'' in the ``Record Type'' column cell. This group contains every weapon (including arrows and bolts) found in the game. \item string(``Portable'', ``true'') -- will evaluate to true for all records containing word true inside ``Portable'' column cell. This group contains every portable light sources (lanterns, torches etc.). \end{itemize} -String token can also use regular expressions (regexps) as it's value. This will be described in the ``Advanced'' section. +This is probably enough to create around 90\% string filters you would need. However, this token is even more powerfull -- it accepts regular expressions (also called regexps). Regular expressions is a way to create string criteria that will be matched by one than just one specific value in the column. For instance, you can display both left and right gauntlets with the following expression: ``string("armor type", ".* gauntlet"))`` because ''.*'' in regexps means just: ``anything''. This filter says: please, show me ``any'' gauntlet. There are left and right gauntlets in the morrowind so this will evaluate to true for both. Simple, isn't it? +\\ +Creating regexps can be a difficult and annoying -- especially when you need complex criteria. On the other hand, We are under impression that in reality complex expressions are needed only in sporadic cases. In fact, the truth is that mostly the mentioned ``.*'' is needed and therefore the following description of regexps can be skipped by vast majority of readers. + +%TO-DO: write the regexps essentials. \paragraph{Value -- value(``value'', (``open'', ``close''))} While string token covers vast group of columns containing string values, there are in fact columns with just numerical values like ``weight``. To filter those we need a value token. This one works in similar manner to the string filter: first token name and criteria inside brackets. Clearly, conditions should hold column to test in. However in this case wanted value is specified as a range. \ No newline at end of file diff --git a/manual/opencs/tables.tex b/manual/opencs/tables.tex new file mode 100644 index 0000000000..eab308d420 --- /dev/null +++ b/manual/opencs/tables.tex @@ -0,0 +1,10 @@ +\section{Tables} +If you launched OpenCS already and played it with for a while you surely noticed that it is a very table oriented application. Your impression is surely correct: major part of Open{CS} is built around table pattern. But this is not excel clone! Table was just the most logical way of dealing with all different record types in a general way. +\subsection{Used Terms} + +\begin{description} +\end{description} + +\subsection{Basics} + +\subsection{Advanced} From 2ca7f247317ea2270df6f816c1a209ac1360efef Mon Sep 17 00:00:00 2001 From: graffy76 Date: Thu, 24 Oct 2013 17:33:28 -0500 Subject: [PATCH 113/434] Fixed filedialog new / edit content path issue --- apps/opencs/editor.cpp | 11 +++--- apps/opencs/view/doc/filedialog.cpp | 2 +- .../contentselector/model/contentmodel.cpp | 1 - .../contentselector/view/contentselector.cpp | 34 ++++++++++++------- .../contentselector/view/contentselector.hpp | 2 ++ 5 files changed, 29 insertions(+), 21 deletions(-) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 1099226d2c..6396563f23 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -126,12 +126,9 @@ void CS::Editor::openFiles() files.push_back(path.toStdString()); } - foreach (const boost::filesystem::path fp, files) - qDebug() << "loading files: " << fp.c_str(); + boost::filesystem::path savePath = mFileDialog.filename().toStdString(); - /// \todo Get the save path from the file dialogue - - CSMDoc::Document *document = mDocumentManager.addDocument (files, *files.rbegin(), false); + CSMDoc::Document *document = mDocumentManager.addDocument (files, savePath, false); mViewManager.addView (document); mFileDialog.hide(); @@ -147,9 +144,9 @@ void CS::Editor::createNewFile() files.push_back(mFileDialog.filename().toStdString()); - /// \todo Get the save path from the file dialogue. + boost::filesystem::path savePath = mFileDialog.filename().toStdString(); - CSMDoc::Document *document = mDocumentManager.addDocument (files, *files.rbegin(), true); + CSMDoc::Document *document = mDocumentManager.addDocument (files, savePath, true); mViewManager.addView (document); mFileDialog.hide(); diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index 1ac476cb29..e82cc30cbf 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -120,7 +120,7 @@ QString CSVDoc::FileDialog::filename() const if (mDialogType == DialogType_New) return mFileWidget->getName(); - return QString (""); + return mSelector->currentFile(); } void CSVDoc::FileDialog::slotRejected() diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index accc149cbb..0674642eed 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -503,7 +503,6 @@ bool ContentSelectorModel::ContentModel::isChecked(const QString& name) const void ContentSelectorModel::ContentModel::setCheckState(const QString &name, bool checkState) { - if (name.isEmpty()) return; diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index 3d615906d8..33b31b00c5 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -9,10 +9,9 @@ #include #include +#include #include -#include - ContentSelectorView::ContentSelector::ContentSelector(QWidget *parent) : QObject(parent) { @@ -26,7 +25,6 @@ ContentSelectorView::ContentSelector::ContentSelector(QWidget *parent) : void ContentSelectorView::ContentSelector::buildContentModel() { mContentModel = new ContentSelectorModel::ContentModel(); - //connect(mContentModel, SIGNAL(layoutChanged()), this, SLOT(updateViews())); } void ContentSelectorView::ContentSelector::buildGameFileView() @@ -41,7 +39,7 @@ void ContentSelectorView::ContentSelector::buildGameFileView() ui.gameFileView->setPlaceholderText(QString("Select a game file...")); ui.gameFileView->setModel(mGameFileProxyModel); - connect (ui.gameFileView, SIGNAL(currentIndexChanged(int)), + connect (ui.gameFileView, SIGNAL (currentIndexChanged(int)), this, SLOT (slotCurrentGameFileIndexChanged(int))); connect (ui.gameFileView, SIGNAL (currentIndexChanged (int)), @@ -64,9 +62,6 @@ void ContentSelectorView::ContentSelector::buildAddonView() ui.addonView->setModel(mAddonProxyModel); connect(ui.addonView, SIGNAL(clicked(const QModelIndex &)), this, SLOT(slotAddonTableItemClicked(const QModelIndex &))); - - for (int i = 0; i < mAddonProxyModel->rowCount(); ++i) - qDebug() << mAddonProxyModel->data(mAddonProxyModel->index(i,0,QModelIndex())); } void ContentSelectorView::ContentSelector::setGameFile(const QString &filename) @@ -113,6 +108,19 @@ void ContentSelectorView::ContentSelector::addFiles(const QString &path) if (ui.gameFileView->currentIndex() != -1) ui.gameFileView->setCurrentIndex(-1); + + mContentModel->uncheckAll(); +} + +QString ContentSelectorView::ContentSelector::currentFile() const +{ + QModelIndex currentIdx = ui.addonView->currentIndex(); + + if (!currentIdx.isValid()) + return ui.gameFileView->currentText(); + + QModelIndex idx = mContentModel->index(mAddonProxyModel->mapToSource(currentIdx).row(), 0, QModelIndex()); + return mContentModel->data(idx, Qt::DisplayRole).toString(); } void ContentSelectorView::ContentSelector::slotCurrentGameFileIndexChanged(int index) @@ -125,12 +133,15 @@ void ContentSelectorView::ContentSelector::slotCurrentGameFileIndexChanged(int i if (proxy) proxy->setDynamicSortFilter(false); - if (oldIndex > -1) - model->setData(model->index(oldIndex, 0), false, Qt::UserRole + 1); + if (index != oldIndex) + { + if (oldIndex > -1) + model->setData(model->index(oldIndex, 0), false, Qt::UserRole + 1); - oldIndex = index; + oldIndex = index; - model->setData(model->index(index, 0), true, Qt::UserRole + 1); + model->setData(model->index(index, 0), true, Qt::UserRole + 1); + } if (proxy) proxy->setDynamicSortFilter(true); @@ -139,7 +150,6 @@ void ContentSelectorView::ContentSelector::slotCurrentGameFileIndexChanged(int i void ContentSelectorView::ContentSelector::slotAddonTableItemClicked(const QModelIndex &index) { QAbstractItemModel *const model = ui.addonView->model(); - //QSortFilterProxyModel *proxy = dynamic_cast(model); if (model->data(index, Qt::CheckStateRole).toInt() == Qt::Unchecked) model->setData(index, Qt::Checked, Qt::CheckStateRole); diff --git a/components/contentselector/view/contentselector.hpp b/components/contentselector/view/contentselector.hpp index 163b19855d..1e24a5523e 100644 --- a/components/contentselector/view/contentselector.hpp +++ b/components/contentselector/view/contentselector.hpp @@ -26,6 +26,8 @@ namespace ContentSelectorView explicit ContentSelector(QWidget *parent = 0); + QString currentFile() const; + void addFiles(const QString &path); void clearCheckStates(); From 1a23cccce3a067943f27b2a2a00b18f175f867c7 Mon Sep 17 00:00:00 2001 From: graffy76 Date: Fri, 25 Oct 2013 11:17:26 -0500 Subject: [PATCH 114/434] Implemented Launcher namespace --- apps/launcher/datafilespage.cpp | 44 +++--- apps/launcher/datafilespage.hpp | 94 ++++++------- apps/launcher/graphicspage.cpp | 24 ++-- apps/launcher/graphicspage.hpp | 67 ++++----- apps/launcher/main.cpp | 3 +- apps/launcher/maindialog.cpp | 41 +++--- apps/launcher/maindialog.hpp | 100 +++++++------- apps/launcher/playpage.cpp | 8 +- apps/launcher/playpage.hpp | 30 ++-- apps/launcher/settings/gamesettings.cpp | 12 +- apps/launcher/settings/gamesettings.hpp | 76 +++++----- apps/launcher/settings/graphicssettings.cpp | 6 +- apps/launcher/settings/graphicssettings.hpp | 16 ++- apps/launcher/settings/launchersettings.cpp | 10 +- apps/launcher/settings/launchersettings.hpp | 20 +-- apps/launcher/settings/settingsbase.hpp | 146 ++++++++++---------- apps/launcher/textslotmsgbox.cpp | 2 +- apps/launcher/textslotmsgbox.hpp | 14 +- apps/launcher/unshieldthread.cpp | 26 ++-- apps/launcher/unshieldthread.hpp | 66 ++++----- apps/launcher/utils/checkablemessagebox.cpp | 139 +++++++++---------- apps/launcher/utils/checkablemessagebox.hpp | 110 ++++++++------- apps/launcher/utils/textinputdialog.cpp | 10 +- apps/launcher/utils/textinputdialog.hpp | 49 +++---- 24 files changed, 573 insertions(+), 540 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index ca404c5d8b..e246b45154 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -21,7 +21,7 @@ #include "components/contentselector/view/contentselector.hpp" -DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gameSettings, LauncherSettings &launcherSettings, QWidget *parent) +Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gameSettings, LauncherSettings &launcherSettings, QWidget *parent) : mCfgMgr(cfg) , mGameSettings(gameSettings) , mLauncherSettings(launcherSettings) @@ -35,7 +35,7 @@ DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSettings &gam setupDataFiles(); } -void DataFilesPage::loadSettings() +void Launcher::DataFilesPage::loadSettings() { QString profileName = ui.profilesComboBox->currentText(); @@ -53,7 +53,7 @@ void DataFilesPage::loadSettings() mSelector->setCheckStates(addons); } -void DataFilesPage::saveSettings(const QString &profile) +void Launcher::DataFilesPage::saveSettings(const QString &profile) { QString profileName = profile; @@ -84,7 +84,7 @@ void DataFilesPage::saveSettings(const QString &profile) } -void DataFilesPage::buildView() +void Launcher::DataFilesPage::buildView() { ui.verticalLayout->insertWidget (0, mSelector->uiWidget()); @@ -111,13 +111,23 @@ void DataFilesPage::buildView() this, SLOT (slotProfileChangedByUser(QString, QString))); } -void DataFilesPage::removeProfile(const QString &profile) +void Launcher::DataFilesPage::removeProfile(const QString &profile) { mLauncherSettings.remove(QString("Profiles/") + profile + QString("/game")); mLauncherSettings.remove(QString("Profiles/") + profile + QString("/addon")); } -void DataFilesPage::setProfile(int index, bool savePrevious) +QAbstractItemModel *Launcher::DataFilesPage::profilesModel() const +{ + return ui.profilesComboBox->model(); +} + +int Launcher::DataFilesPage::profilesIndex() const +{ + return ui.profilesComboBox->currentIndex(); +} + +void Launcher::DataFilesPage::setProfile(int index, bool savePrevious) { if (index >= -1 && index < ui.profilesComboBox->count()) { @@ -128,7 +138,7 @@ void DataFilesPage::setProfile(int index, bool savePrevious) } } -void DataFilesPage::setProfile (const QString &previous, const QString ¤t, bool savePrevious) +void Launcher::DataFilesPage::setProfile (const QString &previous, const QString ¤t, bool savePrevious) { //abort if no change (poss. duplicate signal) if (previous == current) @@ -144,18 +154,18 @@ void DataFilesPage::setProfile (const QString &previous, const QString ¤t, checkForDefaultProfile(); } -void DataFilesPage::slotProfileDeleted (const QString &item) +void Launcher::DataFilesPage::slotProfileDeleted (const QString &item) { removeProfile (item); } -void DataFilesPage::slotProfileChangedByUser(const QString &previous, const QString ¤t) +void Launcher::DataFilesPage::slotProfileChangedByUser(const QString &previous, const QString ¤t) { setProfile(previous, current, true); emit signalProfileChanged (ui.profilesComboBox->findText(current)); } -void DataFilesPage::slotProfileRenamed(const QString &previous, const QString ¤t) +void Launcher::DataFilesPage::slotProfileRenamed(const QString &previous, const QString ¤t) { if (previous.isEmpty()) return; @@ -169,12 +179,12 @@ void DataFilesPage::slotProfileRenamed(const QString &previous, const QString &c loadSettings(); } -void DataFilesPage::slotProfileChanged(int index) +void Launcher::DataFilesPage::slotProfileChanged(int index) { setProfile (index, true); } -void DataFilesPage::setupDataFiles() +void Launcher::DataFilesPage::setupDataFiles() { QStringList paths = mGameSettings.getDataDirs(); @@ -197,7 +207,7 @@ void DataFilesPage::setupDataFiles() loadSettings(); } -void DataFilesPage::on_newProfileAction_triggered() +void Launcher::DataFilesPage::on_newProfileAction_triggered() { TextInputDialog newDialog (tr("New Profile"), tr("Profile name:"), this); @@ -222,7 +232,7 @@ void DataFilesPage::on_newProfileAction_triggered() emit signalProfileChanged (ui.profilesComboBox->findText(profile)); } -void DataFilesPage::addProfile (const QString &profile, bool setAsCurrent) +void Launcher::DataFilesPage::addProfile (const QString &profile, bool setAsCurrent) { if (profile.isEmpty()) return; @@ -236,7 +246,7 @@ void DataFilesPage::addProfile (const QString &profile, bool setAsCurrent) setProfile (ui.profilesComboBox->findText (profile), false); } -void DataFilesPage::on_deleteProfileAction_triggered() +void Launcher::DataFilesPage::on_deleteProfileAction_triggered() { QString profile = ui.profilesComboBox->currentText(); @@ -254,7 +264,7 @@ void DataFilesPage::on_deleteProfileAction_triggered() checkForDefaultProfile(); } -void DataFilesPage::checkForDefaultProfile() +void Launcher::DataFilesPage::checkForDefaultProfile() { //don't allow deleting "Default" profile bool success = (ui.profilesComboBox->currentText() != "Default"); @@ -263,7 +273,7 @@ void DataFilesPage::checkForDefaultProfile() ui.profilesComboBox->setEditEnabled (success); } -bool DataFilesPage::showDeleteMessageBox (const QString &text) +bool Launcher::DataFilesPage::showDeleteMessageBox (const QString &text) { QMessageBox msgBox(this); msgBox.setWindowTitle(tr("Delete Profile")); diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index cc054a4e47..e394e6f41f 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -3,78 +3,76 @@ #include "ui_datafilespage.h" #include -#include class QSortFilterProxyModel; class QAbstractItemModel; -class QAction; class QMenu; -class TextInputDialog; -class GameSettings; -class LauncherSettings; -class ProfilesComboBox; - namespace Files { struct ConfigurationManager; } namespace ContentSelectorView { class ContentSelector; } -class DataFilesPage : public QWidget +namespace Launcher { - Q_OBJECT + class TextInputDialog; + class GameSettings; + class LauncherSettings; + class ProfilesComboBox; - ContentSelectorView::ContentSelector *mSelector; - Ui::DataFilesPage ui; + class DataFilesPage : public QWidget + { + Q_OBJECT -public: - explicit DataFilesPage (Files::ConfigurationManager &cfg, GameSettings &gameSettings, - LauncherSettings &launcherSettings, QWidget *parent = 0); + ContentSelectorView::ContentSelector *mSelector; + Ui::DataFilesPage ui; - QAbstractItemModel* profilesModel() const - { return ui.profilesComboBox->model(); } + public: + explicit DataFilesPage (Files::ConfigurationManager &cfg, GameSettings &gameSettings, + LauncherSettings &launcherSettings, QWidget *parent = 0); - int profilesIndex() const - { return ui.profilesComboBox->currentIndex(); } + QAbstractItemModel* profilesModel() const; - //void writeConfig(QString profile = QString()); - void saveSettings(const QString &profile = ""); - void loadSettings(); + int profilesIndex() const; -signals: - void signalProfileChanged (int index); + //void writeConfig(QString profile = QString()); + void saveSettings(const QString &profile = ""); + void loadSettings(); -public slots: - void slotProfileChanged (int index); + signals: + void signalProfileChanged (int index); -private slots: + public slots: + void slotProfileChanged (int index); - void slotProfileChangedByUser(const QString &previous, const QString ¤t); - void slotProfileRenamed(const QString &previous, const QString ¤t); - void slotProfileDeleted(const QString &item); + private slots: - void on_newProfileAction_triggered(); - void on_deleteProfileAction_triggered(); + void slotProfileChangedByUser(const QString &previous, const QString ¤t); + void slotProfileRenamed(const QString &previous, const QString ¤t); + void slotProfileDeleted(const QString &item); -private: + void on_newProfileAction_triggered(); + void on_deleteProfileAction_triggered(); - QMenu *mContextMenu; + private: - Files::ConfigurationManager &mCfgMgr; + QMenu *mContextMenu; - GameSettings &mGameSettings; - LauncherSettings &mLauncherSettings; + Files::ConfigurationManager &mCfgMgr; - void setPluginsCheckstates(Qt::CheckState state); + GameSettings &mGameSettings; + LauncherSettings &mLauncherSettings; - void buildView(); - void setupDataFiles(); - void setupConfig(); - void readConfig(); - void setProfile (int index, bool savePrevious); - void setProfile (const QString &previous, const QString ¤t, bool savePrevious); - void removeProfile (const QString &profile); - bool showDeleteMessageBox (const QString &text); - void addProfile (const QString &profile, bool setAsCurrent); - void checkForDefaultProfile(); -}; + void setPluginsCheckstates(Qt::CheckState state); + void buildView(); + void setupDataFiles(); + void setupConfig(); + void readConfig(); + void setProfile (int index, bool savePrevious); + void setProfile (const QString &previous, const QString ¤t, bool savePrevious); + void removeProfile (const QString &profile); + bool showDeleteMessageBox (const QString &text); + void addProfile (const QString &profile, bool setAsCurrent); + void checkForDefaultProfile(); + }; +} #endif diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index 2c6c711ea9..1ed1abaebf 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -33,7 +33,7 @@ QString getAspect(int x, int y) return QString(QString::number(xaspect) + ":" + QString::number(yaspect)); } -GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, GraphicsSettings &graphicsSetting, QWidget *parent) +Launcher::GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, GraphicsSettings &graphicsSetting, QWidget *parent) : mCfgMgr(cfg) , mGraphicsSettings(graphicsSetting) , QWidget(parent) @@ -53,7 +53,7 @@ GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, GraphicsSettings &g } -bool GraphicsPage::setupOgre() +bool Launcher::GraphicsPage::setupOgre() { // Create a log manager so we can surpress debug text to stdout/stderr Ogre::LogManager* logMgr = OGRE_NEW Ogre::LogManager; @@ -157,7 +157,7 @@ bool GraphicsPage::setupOgre() return true; } -bool GraphicsPage::setupSDL() +bool Launcher::GraphicsPage::setupSDL() { int displays = SDL_GetNumVideoDisplays(); @@ -180,7 +180,7 @@ bool GraphicsPage::setupSDL() return true; } -bool GraphicsPage::loadSettings() +bool Launcher::GraphicsPage::loadSettings() { if (!setupSDL()) return false; @@ -219,7 +219,7 @@ bool GraphicsPage::loadSettings() return true; } -void GraphicsPage::saveSettings() +void Launcher::GraphicsPage::saveSettings() { vSyncCheckBox->checkState() ? mGraphicsSettings.setValue(QString("Video/vsync"), QString("true")) : mGraphicsSettings.setValue(QString("Video/vsync"), QString("false")); @@ -246,7 +246,7 @@ void GraphicsPage::saveSettings() mGraphicsSettings.setValue(QString("Video/screen"), QString::number(screenComboBox->currentIndex())); } -QStringList GraphicsPage::getAvailableOptions(const QString &key, Ogre::RenderSystem *renderer) +QStringList Launcher::GraphicsPage::getAvailableOptions(const QString &key, Ogre::RenderSystem *renderer) { QStringList result; @@ -279,7 +279,7 @@ QStringList GraphicsPage::getAvailableOptions(const QString &key, Ogre::RenderSy return result; } -QStringList GraphicsPage::getAvailableResolutions(int screen) +QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen) { QStringList result; SDL_DisplayMode mode; @@ -326,7 +326,7 @@ QStringList GraphicsPage::getAvailableResolutions(int screen) return result; } -QRect GraphicsPage::getMaximumResolution() +QRect Launcher::GraphicsPage::getMaximumResolution() { QRect max; int screens = QApplication::desktop()->screenCount(); @@ -341,7 +341,7 @@ QRect GraphicsPage::getMaximumResolution() return max; } -void GraphicsPage::rendererChanged(const QString &renderer) +void Launcher::GraphicsPage::rendererChanged(const QString &renderer) { mSelectedRenderSystem = mOgre->getRenderSystemByName(renderer.toStdString()); @@ -350,7 +350,7 @@ void GraphicsPage::rendererChanged(const QString &renderer) antiAliasingComboBox->addItems(getAvailableOptions(QString("FSAA"), mSelectedRenderSystem)); } -void GraphicsPage::screenChanged(int screen) +void Launcher::GraphicsPage::screenChanged(int screen) { if (screen >= 0) { resolutionComboBox->clear(); @@ -358,7 +358,7 @@ void GraphicsPage::screenChanged(int screen) } } -void GraphicsPage::slotFullScreenChanged(int state) +void Launcher::GraphicsPage::slotFullScreenChanged(int state) { if (state == Qt::Checked) { standardRadioButton->toggle(); @@ -372,7 +372,7 @@ void GraphicsPage::slotFullScreenChanged(int state) } } -void GraphicsPage::slotStandardToggled(bool checked) +void Launcher::GraphicsPage::slotStandardToggled(bool checked) { if (checked) { resolutionComboBox->setEnabled(true); diff --git a/apps/launcher/graphicspage.hpp b/apps/launcher/graphicspage.hpp index d233ea12e2..7f5dcae1ee 100644 --- a/apps/launcher/graphicspage.hpp +++ b/apps/launcher/graphicspage.hpp @@ -18,49 +18,52 @@ #include "ui_graphicspage.h" -class GraphicsSettings; namespace Files { struct ConfigurationManager; } -class GraphicsPage : public QWidget, private Ui::GraphicsPage +namespace Launcher { - Q_OBJECT + class GraphicsSettings; -public: - GraphicsPage(Files::ConfigurationManager &cfg, GraphicsSettings &graphicsSettings, QWidget *parent = 0); + class GraphicsPage : public QWidget, private Ui::GraphicsPage + { + Q_OBJECT - void saveSettings(); - bool loadSettings(); + public: + GraphicsPage(Files::ConfigurationManager &cfg, GraphicsSettings &graphicsSettings, QWidget *parent = 0); -public slots: - void rendererChanged(const QString &renderer); - void screenChanged(int screen); + void saveSettings(); + bool loadSettings(); -private slots: - void slotFullScreenChanged(int state); - void slotStandardToggled(bool checked); + public slots: + void rendererChanged(const QString &renderer); + void screenChanged(int screen); -private: - Ogre::Root *mOgre; - Ogre::RenderSystem *mSelectedRenderSystem; - Ogre::RenderSystem *mOpenGLRenderSystem; - Ogre::RenderSystem *mDirect3DRenderSystem; - #ifdef ENABLE_PLUGIN_GL - Ogre::GLPlugin* mGLPlugin; - #endif - #ifdef ENABLE_PLUGIN_Direct3D9 - Ogre::D3D9Plugin* mD3D9Plugin; - #endif + private slots: + void slotFullScreenChanged(int state); + void slotStandardToggled(bool checked); - Files::ConfigurationManager &mCfgMgr; - GraphicsSettings &mGraphicsSettings; + private: + Ogre::Root *mOgre; + Ogre::RenderSystem *mSelectedRenderSystem; + Ogre::RenderSystem *mOpenGLRenderSystem; + Ogre::RenderSystem *mDirect3DRenderSystem; + #ifdef ENABLE_PLUGIN_GL + Ogre::GLPlugin* mGLPlugin; + #endif + #ifdef ENABLE_PLUGIN_Direct3D9 + Ogre::D3D9Plugin* mD3D9Plugin; + #endif - QStringList getAvailableOptions(const QString &key, Ogre::RenderSystem *renderer); - QStringList getAvailableResolutions(int screen); - QRect getMaximumResolution(); + Files::ConfigurationManager &mCfgMgr; + GraphicsSettings &mGraphicsSettings; - bool setupOgre(); - bool setupSDL(); -}; + QStringList getAvailableOptions(const QString &key, Ogre::RenderSystem *renderer); + QStringList getAvailableResolutions(int screen); + QRect getMaximumResolution(); + bool setupOgre(); + bool setupSDL(); + }; +} #endif diff --git a/apps/launcher/main.cpp b/apps/launcher/main.cpp index f67f5edcff..0b5e62a66f 100644 --- a/apps/launcher/main.cpp +++ b/apps/launcher/main.cpp @@ -49,7 +49,7 @@ int main(int argc, char *argv[]) // Support non-latin characters QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8")); - MainDialog mainWin; + Launcher::MainDialog mainWin; if (mainWin.setup()) { mainWin.show(); @@ -61,4 +61,3 @@ int main(int argc, char *argv[]) SDL_Quit(); return returnValue; } - diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index e514755fea..dca9720ac7 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -1,5 +1,6 @@ #include "maindialog.hpp" +#include #include #include #include @@ -23,8 +24,8 @@ #include "graphicspage.hpp" #include "datafilespage.hpp" -MainDialog::MainDialog() - : mGameSettings(mCfgMgr) +Launcher::MainDialog::MainDialog(QWidget *parent) + : mGameSettings(mCfgMgr), QMainWindow (parent) { // Install the stylesheet font QFile file; @@ -69,7 +70,7 @@ MainDialog::MainDialog() createIcons(); } -void MainDialog::createIcons() +void Launcher::MainDialog::createIcons() { if (!QIcon::hasThemeIcon("document-new")) QIcon::setThemeName("tango"); @@ -101,7 +102,7 @@ void MainDialog::createIcons() } -void MainDialog::createPages() +void Launcher::MainDialog::createPages() { mPlayPage = new PlayPage(this); mGraphicsPage = new GraphicsPage(mCfgMgr, mGraphicsSettings, this); @@ -126,7 +127,7 @@ void MainDialog::createPages() } -bool MainDialog::showFirstRunDialog() +bool Launcher::MainDialog::showFirstRunDialog() { QStringList iniPaths; @@ -282,7 +283,7 @@ bool MainDialog::showFirstRunDialog() return true; } -bool MainDialog::setup() +bool Launcher::MainDialog::setup() { if (!setupLauncherSettings()) return false; @@ -311,7 +312,7 @@ bool MainDialog::setup() return true; } -void MainDialog::changePage(QListWidgetItem *current, QListWidgetItem *previous) +void Launcher::MainDialog::changePage(QListWidgetItem *current, QListWidgetItem *previous) { if (!current) current = previous; @@ -337,7 +338,7 @@ void MainDialog::changePage(QListWidgetItem *current, QListWidgetItem *previous) } } -bool MainDialog::setupLauncherSettings() +bool Launcher::MainDialog::setupLauncherSettings() { mLauncherSettings.setMultiValueEnabled(true); @@ -374,7 +375,7 @@ bool MainDialog::setupLauncherSettings() } #ifndef WIN32 -bool expansions(UnshieldThread& cd) +bool Launcher::expansions(Launcher::UnshieldThread& cd) { if(cd.BloodmoonDone()) { @@ -385,7 +386,7 @@ bool expansions(UnshieldThread& cd) QMessageBox expansionsBox; expansionsBox.setText(QObject::tr("
Would you like to install expansions now ? (make sure you have the disc)
\ If you want to install both Bloodmoon and Tribunal, you have to install Tribunal first.
")); - + QAbstractButton* tribunalButton = NULL; if(!cd.TribunalDone()) tribunalButton = expansionsBox.addButton(QObject::tr("&Tribunal"), QMessageBox::ActionRole); @@ -404,7 +405,7 @@ bool expansions(UnshieldThread& cd) { TextSlotMsgBox cdbox; - cdbox.setStandardButtons(QMessageBox::Cancel); + cdbox.setStandardButtons(QMessageBox::Cancel); QObject::connect(&cd,SIGNAL(signalGUI(const QString&)), &cdbox, SLOT(setTextSlot(const QString&))); QObject::connect(&cd,SIGNAL(close()), &cdbox, SLOT(reject())); @@ -423,7 +424,7 @@ bool expansions(UnshieldThread& cd) { TextSlotMsgBox cdbox; - cdbox.setStandardButtons(QMessageBox::Cancel); + cdbox.setStandardButtons(QMessageBox::Cancel); QObject::connect(&cd,SIGNAL(signalGUI(const QString&)), &cdbox, SLOT(setTextSlot(const QString&))); QObject::connect(&cd,SIGNAL(close()), &cdbox, SLOT(reject())); @@ -445,7 +446,7 @@ bool expansions(UnshieldThread& cd) } #endif // WIN32 -bool MainDialog::setupGameSettings() +bool Launcher::MainDialog::setupGameSettings() { QString userPath = QString::fromStdString(mCfgMgr.getUserPath().string()); QString globalPath = QString::fromStdString(mCfgMgr.getGlobalPath().string()); @@ -568,7 +569,7 @@ bool MainDialog::setupGameSettings() return true; } -bool MainDialog::setupGraphicsSettings() +bool Launcher::MainDialog::setupGraphicsSettings() { mGraphicsSettings.setMultiValueEnabled(false); @@ -622,7 +623,7 @@ bool MainDialog::setupGraphicsSettings() return true; } -void MainDialog::loadSettings() +void Launcher::MainDialog::loadSettings() { int width = mLauncherSettings.value(QString("General/MainWindow/width")).toInt(); int height = mLauncherSettings.value(QString("General/MainWindow/height")).toInt(); @@ -634,7 +635,7 @@ void MainDialog::loadSettings() move(posX, posY); } -void MainDialog::saveSettings() +void Launcher::MainDialog::saveSettings() { QString width = QString::number(this->width()); QString height = QString::number(this->height()); @@ -652,7 +653,7 @@ void MainDialog::saveSettings() } -bool MainDialog::writeSettings() +bool Launcher::MainDialog::writeSettings() { // Now write all config files saveSettings(); @@ -745,13 +746,13 @@ bool MainDialog::writeSettings() return true; } -void MainDialog::closeEvent(QCloseEvent *event) +void Launcher::MainDialog::closeEvent(QCloseEvent *event) { writeSettings(); event->accept(); } -void MainDialog::play() +void Launcher::MainDialog::play() { if (!writeSettings()) { qApp->quit(); @@ -774,7 +775,7 @@ void MainDialog::play() qApp->quit(); } -bool MainDialog::startProgram(const QString &name, const QStringList &arguments, bool detached) +bool Launcher::MainDialog::startProgram(const QString &name, const QStringList &arguments, bool detached) { QString path = name; #ifdef Q_OS_WIN diff --git a/apps/launcher/maindialog.hpp b/apps/launcher/maindialog.hpp index 824dff6e82..5b8e4908e7 100644 --- a/apps/launcher/maindialog.hpp +++ b/apps/launcher/maindialog.hpp @@ -11,57 +11,59 @@ #include "ui_mainwindow.h" -class QListWidget; class QListWidgetItem; -class QStackedWidget; -class QStringList; -class QStringListModel; -class QString; -class PlayPage; -class GraphicsPage; -class DataFilesPage; - -class MainDialog : public QMainWindow, private Ui::MainWindow +namespace Launcher { - Q_OBJECT - -public: - MainDialog(); - bool setup(); - bool showFirstRunDialog(); - -public slots: - void changePage(QListWidgetItem *current, QListWidgetItem *previous); - void play(); - -private: - void createIcons(); - void createPages(); - - bool setupLauncherSettings(); - bool setupGameSettings(); - bool setupGraphicsSettings(); - - void loadSettings(); - void saveSettings(); - bool writeSettings(); - - inline bool startProgram(const QString &name, bool detached = false) { return startProgram(name, QStringList(), detached); } - bool startProgram(const QString &name, const QStringList &arguments, bool detached = false); - - void closeEvent(QCloseEvent *event); - - PlayPage *mPlayPage; - GraphicsPage *mGraphicsPage; - DataFilesPage *mDataFilesPage; - - Files::ConfigurationManager mCfgMgr; - - GameSettings mGameSettings; - GraphicsSettings mGraphicsSettings; - LauncherSettings mLauncherSettings; - -}; + class PlayPage; + class GraphicsPage; + class DataFilesPage; + class UnshieldThread; +#ifndef WIN32 + bool expansions(Launcher::UnshieldThread& cd); +#endif + + class MainDialog : public QMainWindow, private Ui::MainWindow + { + Q_OBJECT + + public: + explicit MainDialog(QWidget *parent = 0); + bool setup(); + bool showFirstRunDialog(); + + public slots: + void changePage(QListWidgetItem *current, QListWidgetItem *previous); + void play(); + + private: + void createIcons(); + void createPages(); + + bool setupLauncherSettings(); + bool setupGameSettings(); + bool setupGraphicsSettings(); + + void loadSettings(); + void saveSettings(); + bool writeSettings(); + + inline bool startProgram(const QString &name, bool detached = false) { return startProgram(name, QStringList(), detached); } + bool startProgram(const QString &name, const QStringList &arguments, bool detached = false); + + void closeEvent(QCloseEvent *event); + + PlayPage *mPlayPage; + GraphicsPage *mGraphicsPage; + DataFilesPage *mDataFilesPage; + + Files::ConfigurationManager mCfgMgr; + + GameSettings mGameSettings; + GraphicsSettings mGraphicsSettings; + LauncherSettings mLauncherSettings; + + }; +} #endif diff --git a/apps/launcher/playpage.cpp b/apps/launcher/playpage.cpp index fc1ed1c69b..6cfb9686fa 100644 --- a/apps/launcher/playpage.cpp +++ b/apps/launcher/playpage.cpp @@ -6,7 +6,7 @@ #include #endif -PlayPage::PlayPage(QWidget *parent) : QWidget(parent) +Launcher::PlayPage::PlayPage(QWidget *parent) : QWidget(parent) { setObjectName ("PlayPage"); setupUi(this); @@ -23,17 +23,17 @@ PlayPage::PlayPage(QWidget *parent) : QWidget(parent) } -void PlayPage::setProfilesModel(QAbstractItemModel *model) +void Launcher::PlayPage::setProfilesModel(QAbstractItemModel *model) { profilesComboBox->setModel(model); } -void PlayPage::setProfilesIndex(int index) +void Launcher::PlayPage::setProfilesIndex(int index) { profilesComboBox->setCurrentIndex(index); } -void PlayPage::slotPlayClicked() +void Launcher::PlayPage::slotPlayClicked() { emit playButtonClicked(); } diff --git a/apps/launcher/playpage.hpp b/apps/launcher/playpage.hpp index 42edfadb18..1dc5bb0fe0 100644 --- a/apps/launcher/playpage.hpp +++ b/apps/launcher/playpage.hpp @@ -9,26 +9,28 @@ class QComboBox; class QPushButton; class QAbstractItemModel; -class PlayPage : public QWidget, private Ui::PlayPage +namespace Launcher { - Q_OBJECT + class PlayPage : public QWidget, private Ui::PlayPage + { + Q_OBJECT -public: - PlayPage(QWidget *parent = 0); - void setProfilesModel(QAbstractItemModel *model); + public: + PlayPage(QWidget *parent = 0); + void setProfilesModel(QAbstractItemModel *model); -signals: - void signalProfileChanged(int index); - void playButtonClicked(); + signals: + void signalProfileChanged(int index); + void playButtonClicked(); -public slots: - void setProfilesIndex(int index); + public slots: + void setProfilesIndex(int index); -private slots: - void slotPlayClicked(); + private slots: + void slotPlayClicked(); -}; - + }; +} #endif diff --git a/apps/launcher/settings/gamesettings.cpp b/apps/launcher/settings/gamesettings.cpp index 205879bc37..5231753f2d 100644 --- a/apps/launcher/settings/gamesettings.cpp +++ b/apps/launcher/settings/gamesettings.cpp @@ -26,16 +26,16 @@ namespace boost #endif /* (BOOST_VERSION <= 104600) */ -GameSettings::GameSettings(Files::ConfigurationManager &cfg) +Launcher::GameSettings::GameSettings(Files::ConfigurationManager &cfg) : mCfgMgr(cfg) { } -GameSettings::~GameSettings() +Launcher::GameSettings::~GameSettings() { } -void GameSettings::validatePaths() +void Launcher::GameSettings::validatePaths() { if (mSettings.isEmpty() || !mDataDirs.isEmpty()) return; // Don't re-validate paths if they are already parsed @@ -81,14 +81,14 @@ void GameSettings::validatePaths() } } -QStringList GameSettings::values(const QString &key, const QStringList &defaultValues) +QStringList Launcher::GameSettings::values(const QString &key, const QStringList &defaultValues) { if (!mSettings.values(key).isEmpty()) return mSettings.values(key); return defaultValues; } -bool GameSettings::readFile(QTextStream &stream) +bool Launcher::GameSettings::readFile(QTextStream &stream) { QMap cache; QRegExp keyRe("^([^=]+)\\s*=\\s*(.+)$"); @@ -130,7 +130,7 @@ bool GameSettings::readFile(QTextStream &stream) return true; } -bool GameSettings::writeFile(QTextStream &stream) +bool Launcher::GameSettings::writeFile(QTextStream &stream) { // Iterate in reverse order to preserve insertion order QMapIterator i(mSettings); diff --git a/apps/launcher/settings/gamesettings.hpp b/apps/launcher/settings/gamesettings.hpp index 55b2107e2a..11f06b027c 100644 --- a/apps/launcher/settings/gamesettings.hpp +++ b/apps/launcher/settings/gamesettings.hpp @@ -11,52 +11,54 @@ namespace Files { typedef std::vector PathContainer; struct ConfigurationManager;} -class GameSettings +namespace Launcher { -public: - GameSettings(Files::ConfigurationManager &cfg); - ~GameSettings(); - - inline QString value(const QString &key, const QString &defaultValue = QString()) + class GameSettings { - return mSettings.value(key).isEmpty() ? defaultValue : mSettings.value(key); - } + public: + GameSettings(Files::ConfigurationManager &cfg); + ~GameSettings(); + + inline QString value(const QString &key, const QString &defaultValue = QString()) + { + return mSettings.value(key).isEmpty() ? defaultValue : mSettings.value(key); + } - inline void setValue(const QString &key, const QString &value) - { - mSettings.insert(key, value); - } + inline void setValue(const QString &key, const QString &value) + { + mSettings.insert(key, value); + } - inline void setMultiValue(const QString &key, const QString &value) - { - QStringList values = mSettings.values(key); - if (!values.contains(value)) - mSettings.insertMulti(key, value); - } + inline void setMultiValue(const QString &key, const QString &value) + { + QStringList values = mSettings.values(key); + if (!values.contains(value)) + mSettings.insertMulti(key, value); + } - inline void remove(const QString &key) - { - mSettings.remove(key); - } + inline void remove(const QString &key) + { + mSettings.remove(key); + } - inline QStringList getDataDirs() { return mDataDirs; } - inline void addDataDir(const QString &dir) { if(!dir.isEmpty()) mDataDirs.append(dir); } - inline QString getDataLocal() {return mDataLocal; } - inline bool hasMaster() { return mSettings.count(QString("master")) > 0; } + inline QStringList getDataDirs() { return mDataDirs; } + inline void addDataDir(const QString &dir) { if(!dir.isEmpty()) mDataDirs.append(dir); } + inline QString getDataLocal() {return mDataLocal; } + inline bool hasMaster() { return mSettings.count(QString("master")) > 0; } - QStringList values(const QString &key, const QStringList &defaultValues = QStringList()); - bool readFile(QTextStream &stream); - bool writeFile(QTextStream &stream); + QStringList values(const QString &key, const QStringList &defaultValues = QStringList()); + bool readFile(QTextStream &stream); + bool writeFile(QTextStream &stream); -private: - Files::ConfigurationManager &mCfgMgr; + private: + Files::ConfigurationManager &mCfgMgr; - void validatePaths(); - QMap mSettings; - - QStringList mDataDirs; - QString mDataLocal; -}; + void validatePaths(); + QMap mSettings; + QStringList mDataDirs; + QString mDataLocal; + }; +} #endif // GAMESETTINGS_HPP diff --git a/apps/launcher/settings/graphicssettings.cpp b/apps/launcher/settings/graphicssettings.cpp index 0c55800917..9dad3dee6b 100644 --- a/apps/launcher/settings/graphicssettings.cpp +++ b/apps/launcher/settings/graphicssettings.cpp @@ -5,15 +5,15 @@ #include #include -GraphicsSettings::GraphicsSettings() +Launcher::GraphicsSettings::GraphicsSettings() { } -GraphicsSettings::~GraphicsSettings() +Launcher::GraphicsSettings::~GraphicsSettings() { } -bool GraphicsSettings::writeFile(QTextStream &stream) +bool Launcher::GraphicsSettings::writeFile(QTextStream &stream) { QString sectionPrefix; QRegExp sectionRe("([^/]+)/(.+)$"); diff --git a/apps/launcher/settings/graphicssettings.hpp b/apps/launcher/settings/graphicssettings.hpp index 3e8617849e..6f7c135473 100644 --- a/apps/launcher/settings/graphicssettings.hpp +++ b/apps/launcher/settings/graphicssettings.hpp @@ -3,14 +3,16 @@ #include "settingsbase.hpp" -class GraphicsSettings : public SettingsBase > +namespace Launcher { -public: - GraphicsSettings(); - ~GraphicsSettings(); + class GraphicsSettings : public SettingsBase > + { + public: + GraphicsSettings(); + ~GraphicsSettings(); - bool writeFile(QTextStream &stream); - -}; + bool writeFile(QTextStream &stream); + }; +} #endif // GRAPHICSSETTINGS_HPP diff --git a/apps/launcher/settings/launchersettings.cpp b/apps/launcher/settings/launchersettings.cpp index 5d298e814e..7c97144eaf 100644 --- a/apps/launcher/settings/launchersettings.cpp +++ b/apps/launcher/settings/launchersettings.cpp @@ -5,15 +5,15 @@ #include #include -LauncherSettings::LauncherSettings() +Launcher::LauncherSettings::LauncherSettings() { } -LauncherSettings::~LauncherSettings() +Launcher::LauncherSettings::~LauncherSettings() { } -QStringList LauncherSettings::values(const QString &key, Qt::MatchFlags flags) +QStringList Launcher::LauncherSettings::values(const QString &key, Qt::MatchFlags flags) { QMap settings = SettingsBase::getSettings(); @@ -34,7 +34,7 @@ QStringList LauncherSettings::values(const QString &key, Qt::MatchFlags flags) return result; } -QStringList LauncherSettings::subKeys(const QString &key) +QStringList Launcher::LauncherSettings::subKeys(const QString &key) { QMap settings = SettingsBase::getSettings(); QStringList keys = settings.uniqueKeys(); @@ -61,7 +61,7 @@ QStringList LauncherSettings::subKeys(const QString &key) return result; } -bool LauncherSettings::writeFile(QTextStream &stream) +bool Launcher::LauncherSettings::writeFile(QTextStream &stream) { QString sectionPrefix; QRegExp sectionRe("([^/]+)/(.+)$"); diff --git a/apps/launcher/settings/launchersettings.hpp b/apps/launcher/settings/launchersettings.hpp index 60c6f86bc7..8acc389a9d 100644 --- a/apps/launcher/settings/launchersettings.hpp +++ b/apps/launcher/settings/launchersettings.hpp @@ -3,17 +3,19 @@ #include "settingsbase.hpp" -class LauncherSettings : public SettingsBase > +namespace Launcher { -public: - LauncherSettings(); - ~LauncherSettings(); + class LauncherSettings : public SettingsBase > + { + public: + LauncherSettings(); + ~LauncherSettings(); - QStringList subKeys(const QString &key); - QStringList values(const QString &key, Qt::MatchFlags flags = Qt::MatchExactly); + QStringList subKeys(const QString &key); + QStringList values(const QString &key, Qt::MatchFlags flags = Qt::MatchExactly); - bool writeFile(QTextStream &stream); - -}; + bool writeFile(QTextStream &stream); + }; +} #endif // LAUNCHERSETTINGS_HPP diff --git a/apps/launcher/settings/settingsbase.hpp b/apps/launcher/settings/settingsbase.hpp index ed8ada56c3..3a1cf8e30e 100644 --- a/apps/launcher/settings/settingsbase.hpp +++ b/apps/launcher/settings/settingsbase.hpp @@ -7,103 +7,105 @@ #include #include -template -class SettingsBase +namespace Launcher { - -public: - SettingsBase() { mMultiValue = false; } - ~SettingsBase() {} - - inline QString value(const QString &key, const QString &defaultValue = QString()) + template + class SettingsBase { - return mSettings.value(key).isEmpty() ? defaultValue : mSettings.value(key); - } - inline void setValue(const QString &key, const QString &value) - { - QStringList values = mSettings.values(key); - if (!values.contains(value)) - mSettings.insert(key, value); - } + public: + SettingsBase() { mMultiValue = false; } + ~SettingsBase() {} - inline void setMultiValue(const QString &key, const QString &value) - { - QStringList values = mSettings.values(key); - if (!values.contains(value)) - mSettings.insertMulti(key, value); - } + inline QString value(const QString &key, const QString &defaultValue = QString()) + { + return mSettings.value(key).isEmpty() ? defaultValue : mSettings.value(key); + } - inline void setMultiValueEnabled(bool enable) - { - mMultiValue = enable; - } + inline void setValue(const QString &key, const QString &value) + { + QStringList values = mSettings.values(key); + if (!values.contains(value)) + mSettings.insert(key, value); + } - inline void remove(const QString &key) - { - mSettings.remove(key); - } + inline void setMultiValue(const QString &key, const QString &value) + { + QStringList values = mSettings.values(key); + if (!values.contains(value)) + mSettings.insertMulti(key, value); + } - Map getSettings() {return mSettings;} + inline void setMultiValueEnabled(bool enable) + { + mMultiValue = enable; + } - bool readFile(QTextStream &stream) - { - mCache.clear(); + inline void remove(const QString &key) + { + mSettings.remove(key); + } - QString sectionPrefix; + Map getSettings() {return mSettings;} - QRegExp sectionRe("^\\[([^]]+)\\]"); - QRegExp keyRe("^([^=]+)\\s*=\\s*(.+)$"); + bool readFile(QTextStream &stream) + { + mCache.clear(); - while (!stream.atEnd()) { - QString line = stream.readLine(); + QString sectionPrefix; - if (line.isEmpty() || line.startsWith("#")) - continue; + QRegExp sectionRe("^\\[([^]]+)\\]"); + QRegExp keyRe("^([^=]+)\\s*=\\s*(.+)$"); - if (sectionRe.exactMatch(line)) { - sectionPrefix = sectionRe.cap(1); - sectionPrefix.append("/"); - continue; - } + while (!stream.atEnd()) { + QString line = stream.readLine(); - if (keyRe.indexIn(line) != -1) { + if (line.isEmpty() || line.startsWith("#")) + continue; - QString key = keyRe.cap(1).trimmed(); - QString value = keyRe.cap(2).trimmed(); + if (sectionRe.exactMatch(line)) { + sectionPrefix = sectionRe.cap(1); + sectionPrefix.append("/"); + continue; + } - if (!sectionPrefix.isEmpty()) - key.prepend(sectionPrefix); + if (keyRe.indexIn(line) != -1) { - mSettings.remove(key); + QString key = keyRe.cap(1).trimmed(); + QString value = keyRe.cap(2).trimmed(); - QStringList values = mCache.values(key); + if (!sectionPrefix.isEmpty()) + key.prepend(sectionPrefix); - if (!values.contains(value)) { - if (mMultiValue) { - mCache.insertMulti(key, value); - } else { - mCache.insert(key, value); + mSettings.remove(key); + + QStringList values = mCache.values(key); + + if (!values.contains(value)) { + if (mMultiValue) { + mCache.insertMulti(key, value); + } else { + mCache.insert(key, value); + } } } } - } - if (mSettings.isEmpty()) { - mSettings = mCache; // This is the first time we read a file + if (mSettings.isEmpty()) { + mSettings = mCache; // This is the first time we read a file + return true; + } + + // Merge the changed keys with those which didn't + mSettings.unite(mCache); return true; } - // Merge the changed keys with those which didn't - mSettings.unite(mCache); - return true; - } - -private: - Map mSettings; - Map mCache; - - bool mMultiValue; -}; + private: + Map mSettings; + Map mCache; + bool mMultiValue; + }; +} #endif // SETTINGSBASE_HPP diff --git a/apps/launcher/textslotmsgbox.cpp b/apps/launcher/textslotmsgbox.cpp index 0607d1cc6e..62d9cf5761 100644 --- a/apps/launcher/textslotmsgbox.cpp +++ b/apps/launcher/textslotmsgbox.cpp @@ -1,6 +1,6 @@ #include "textslotmsgbox.hpp" -void TextSlotMsgBox::setTextSlot(const QString& string) +void Launcher::TextSlotMsgBox::setTextSlot(const QString& string) { setText(string); } diff --git a/apps/launcher/textslotmsgbox.hpp b/apps/launcher/textslotmsgbox.hpp index a29e2c3543..a0fefaa253 100644 --- a/apps/launcher/textslotmsgbox.hpp +++ b/apps/launcher/textslotmsgbox.hpp @@ -3,11 +3,13 @@ #include -class TextSlotMsgBox : public QMessageBox +namespace Launcher { -Q_OBJECT - public slots: - void setTextSlot(const QString& string); -}; - + class TextSlotMsgBox : public QMessageBox + { + Q_OBJECT + public slots: + void setTextSlot(const QString& string); + }; +} #endif diff --git a/apps/launcher/unshieldthread.cpp b/apps/launcher/unshieldthread.cpp index 69b241365c..d0dbeb1bdb 100644 --- a/apps/launcher/unshieldthread.cpp +++ b/apps/launcher/unshieldthread.cpp @@ -292,30 +292,30 @@ namespace } -bool UnshieldThread::SetMorrowindPath(const std::string& path) +bool Launcher::UnshieldThread::SetMorrowindPath(const std::string& path) { mMorrowindPath = path; return true; } -bool UnshieldThread::SetTribunalPath(const std::string& path) +bool Launcher::UnshieldThread::SetTribunalPath(const std::string& path) { mTribunalPath = path; return true; } -bool UnshieldThread::SetBloodmoonPath(const std::string& path) +bool Launcher::UnshieldThread::SetBloodmoonPath(const std::string& path) { mBloodmoonPath = path; return true; } -void UnshieldThread::SetOutputPath(const std::string& path) +void Launcher::UnshieldThread::SetOutputPath(const std::string& path) { mOutputPath = path; } -bool UnshieldThread::extract_file(Unshield* unshield, bfs::path output_dir, const char* prefix, int index) +bool Launcher::UnshieldThread::extract_file(Unshield* unshield, bfs::path output_dir, const char* prefix, int index) { bool success; bfs::path dirname; @@ -349,7 +349,7 @@ bool UnshieldThread::extract_file(Unshield* unshield, bfs::path output_dir, cons return success; } -void UnshieldThread::extract_cab(const bfs::path& cab, const bfs::path& output_dir, bool extract_ini) +void Launcher::UnshieldThread::extract_cab(const bfs::path& cab, const bfs::path& output_dir, bool extract_ini) { Unshield * unshield; unshield = unshield_open(cab.c_str()); @@ -369,7 +369,7 @@ void UnshieldThread::extract_cab(const bfs::path& cab, const bfs::path& output_d } -bool UnshieldThread::extract() +bool Launcher::UnshieldThread::extract() { bfs::path outputDataFilesDir = mOutputPath; outputDataFilesDir /= "Data Files"; @@ -475,7 +475,7 @@ bool UnshieldThread::extract() return true; } -void UnshieldThread::Done() +void Launcher::UnshieldThread::Done() { // Get rid of unnecessary files bfs::remove_all(mOutputPath / "extract-temp"); @@ -491,28 +491,28 @@ void UnshieldThread::Done() bfs::last_write_time(findFile(mOutputPath, "bloodmoon.esm"), getTime("3 June 2003")); } -std::string UnshieldThread::GetMWEsmPath() +std::string Launcher::UnshieldThread::GetMWEsmPath() { return findFile(mOutputPath / "Data Files", "morrowind.esm").string(); } -bool UnshieldThread::TribunalDone() +bool Launcher::UnshieldThread::TribunalDone() { return mTribunalDone; } -bool UnshieldThread::BloodmoonDone() +bool Launcher::UnshieldThread::BloodmoonDone() { return mBloodmoonDone; } -void UnshieldThread::run() +void Launcher::UnshieldThread::run() { extract(); emit close(); } -UnshieldThread::UnshieldThread() +Launcher::UnshieldThread::UnshieldThread() { unshield_set_log_level(0); mMorrowindDone = false; diff --git a/apps/launcher/unshieldthread.hpp b/apps/launcher/unshieldthread.hpp index 655cb5b536..de6a32b442 100644 --- a/apps/launcher/unshieldthread.hpp +++ b/apps/launcher/unshieldthread.hpp @@ -7,50 +7,52 @@ #include -class UnshieldThread : public QThread +namespace Launcher { - Q_OBJECT + class UnshieldThread : public QThread + { + Q_OBJECT - public: - bool SetMorrowindPath(const std::string& path); - bool SetTribunalPath(const std::string& path); - bool SetBloodmoonPath(const std::string& path); + public: + bool SetMorrowindPath(const std::string& path); + bool SetTribunalPath(const std::string& path); + bool SetBloodmoonPath(const std::string& path); - void SetOutputPath(const std::string& path); - - bool extract(); + void SetOutputPath(const std::string& path); - bool TribunalDone(); - bool BloodmoonDone(); + bool extract(); - void Done(); + bool TribunalDone(); + bool BloodmoonDone(); - std::string GetMWEsmPath(); + void Done(); - UnshieldThread(); + std::string GetMWEsmPath(); - private: + UnshieldThread(); - void extract_cab(const boost::filesystem::path& cab, const boost::filesystem::path& output_dir, bool extract_ini = false); - bool extract_file(Unshield* unshield, boost::filesystem::path output_dir, const char* prefix, int index); - - boost::filesystem::path mMorrowindPath; - boost::filesystem::path mTribunalPath; - boost::filesystem::path mBloodmoonPath; + private: - bool mMorrowindDone; - bool mTribunalDone; - bool mBloodmoonDone; + void extract_cab(const boost::filesystem::path& cab, const boost::filesystem::path& output_dir, bool extract_ini = false); + bool extract_file(Unshield* unshield, boost::filesystem::path output_dir, const char* prefix, int index); - boost::filesystem::path mOutputPath; + boost::filesystem::path mMorrowindPath; + boost::filesystem::path mTribunalPath; + boost::filesystem::path mBloodmoonPath; + + bool mMorrowindDone; + bool mTribunalDone; + bool mBloodmoonDone; + + boost::filesystem::path mOutputPath; - protected: - virtual void run(); - - signals: - void signalGUI(QString); - void close(); -}; + protected: + virtual void run(); + signals: + void signalGUI(QString); + void close(); + }; +} #endif diff --git a/apps/launcher/utils/checkablemessagebox.cpp b/apps/launcher/utils/checkablemessagebox.cpp index 41207a8ded..2f775af57a 100644 --- a/apps/launcher/utils/checkablemessagebox.cpp +++ b/apps/launcher/utils/checkablemessagebox.cpp @@ -54,72 +54,61 @@ Emulates the QMessageBox API with static conveniences. The message label can open external URLs. */ - -class CheckableMessageBoxPrivate -{ -public: - CheckableMessageBoxPrivate(QDialog *q) +Launcher::CheckableMessageBoxPrivate::CheckableMessageBoxPrivate(QDialog *q) : clickedButton(0) - { - QSizePolicy sizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); +{ + QSizePolicy sizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); - pixmapLabel = new QLabel(q); - sizePolicy.setHorizontalStretch(0); - sizePolicy.setVerticalStretch(0); - sizePolicy.setHeightForWidth(pixmapLabel->sizePolicy().hasHeightForWidth()); - pixmapLabel->setSizePolicy(sizePolicy); - pixmapLabel->setVisible(false); + pixmapLabel = new QLabel(q); + sizePolicy.setHorizontalStretch(0); + sizePolicy.setVerticalStretch(0); + sizePolicy.setHeightForWidth(pixmapLabel->sizePolicy().hasHeightForWidth()); + pixmapLabel->setSizePolicy(sizePolicy); + pixmapLabel->setVisible(false); - QSpacerItem *pixmapSpacer = - new QSpacerItem(0, 5, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding); + QSpacerItem *pixmapSpacer = + new QSpacerItem(0, 5, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding); - messageLabel = new QLabel(q); - messageLabel->setMinimumSize(QSize(300, 0)); - messageLabel->setWordWrap(true); - messageLabel->setOpenExternalLinks(true); - messageLabel->setTextInteractionFlags(Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse); + messageLabel = new QLabel(q); + messageLabel->setMinimumSize(QSize(300, 0)); + messageLabel->setWordWrap(true); + messageLabel->setOpenExternalLinks(true); + messageLabel->setTextInteractionFlags(Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse); - QSpacerItem *checkBoxRightSpacer = - new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum); - QSpacerItem *buttonSpacer = - new QSpacerItem(0, 1, QSizePolicy::Minimum, QSizePolicy::Minimum); + QSpacerItem *checkBoxRightSpacer = + new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum); + QSpacerItem *buttonSpacer = + new QSpacerItem(0, 1, QSizePolicy::Minimum, QSizePolicy::Minimum); - checkBox = new QCheckBox(q); - checkBox->setText(CheckableMessageBox::tr("Do not ask again")); + checkBox = new QCheckBox(q); + checkBox->setText(Launcher::CheckableMessageBox::tr("Do not ask again")); - buttonBox = new QDialogButtonBox(q); - buttonBox->setOrientation(Qt::Horizontal); - buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); + buttonBox = new QDialogButtonBox(q); + buttonBox->setOrientation(Qt::Horizontal); + buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); - QVBoxLayout *verticalLayout = new QVBoxLayout(); - verticalLayout->addWidget(pixmapLabel); - verticalLayout->addItem(pixmapSpacer); + QVBoxLayout *verticalLayout = new QVBoxLayout(); + verticalLayout->addWidget(pixmapLabel); + verticalLayout->addItem(pixmapSpacer); - QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); - horizontalLayout_2->addLayout(verticalLayout); - horizontalLayout_2->addWidget(messageLabel); + QHBoxLayout *horizontalLayout_2 = new QHBoxLayout(); + horizontalLayout_2->addLayout(verticalLayout); + horizontalLayout_2->addWidget(messageLabel); - QHBoxLayout *horizontalLayout = new QHBoxLayout(); - horizontalLayout->addWidget(checkBox); - horizontalLayout->addItem(checkBoxRightSpacer); + QHBoxLayout *horizontalLayout = new QHBoxLayout(); + horizontalLayout->addWidget(checkBox); + horizontalLayout->addItem(checkBoxRightSpacer); - QVBoxLayout *verticalLayout_2 = new QVBoxLayout(q); - verticalLayout_2->addLayout(horizontalLayout_2); - verticalLayout_2->addLayout(horizontalLayout); - verticalLayout_2->addItem(buttonSpacer); - verticalLayout_2->addWidget(buttonBox); - } + QVBoxLayout *verticalLayout_2 = new QVBoxLayout(q); + verticalLayout_2->addLayout(horizontalLayout_2); + verticalLayout_2->addLayout(horizontalLayout); + verticalLayout_2->addItem(buttonSpacer); + verticalLayout_2->addWidget(buttonBox); +} - QLabel *pixmapLabel; - QLabel *messageLabel; - QCheckBox *checkBox; - QDialogButtonBox *buttonBox; - QAbstractButton *clickedButton; -}; - -CheckableMessageBox::CheckableMessageBox(QWidget *parent) : +Launcher::CheckableMessageBox::CheckableMessageBox(QWidget *parent) : QDialog(parent), - d(new CheckableMessageBoxPrivate(this)) + d(new Launcher::CheckableMessageBoxPrivate(this)) { setModal(true); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); @@ -129,102 +118,102 @@ CheckableMessageBox::CheckableMessageBox(QWidget *parent) : SLOT(slotClicked(QAbstractButton*))); } -CheckableMessageBox::~CheckableMessageBox() +Launcher::CheckableMessageBox::~CheckableMessageBox() { delete d; } -void CheckableMessageBox::slotClicked(QAbstractButton *b) +void Launcher::CheckableMessageBox::slotClicked(QAbstractButton *b) { d->clickedButton = b; } -QAbstractButton *CheckableMessageBox::clickedButton() const +QAbstractButton *Launcher::CheckableMessageBox::clickedButton() const { return d->clickedButton; } -QDialogButtonBox::StandardButton CheckableMessageBox::clickedStandardButton() const +QDialogButtonBox::StandardButton Launcher::CheckableMessageBox::clickedStandardButton() const { if (d->clickedButton) return d->buttonBox->standardButton(d->clickedButton); return QDialogButtonBox::NoButton; } -QString CheckableMessageBox::text() const +QString Launcher::CheckableMessageBox::text() const { return d->messageLabel->text(); } -void CheckableMessageBox::setText(const QString &t) +void Launcher::CheckableMessageBox::setText(const QString &t) { d->messageLabel->setText(t); } -QPixmap CheckableMessageBox::iconPixmap() const +QPixmap Launcher::CheckableMessageBox::iconPixmap() const { if (const QPixmap *p = d->pixmapLabel->pixmap()) return QPixmap(*p); return QPixmap(); } -void CheckableMessageBox::setIconPixmap(const QPixmap &p) +void Launcher::CheckableMessageBox::setIconPixmap(const QPixmap &p) { d->pixmapLabel->setPixmap(p); d->pixmapLabel->setVisible(!p.isNull()); } -bool CheckableMessageBox::isChecked() const +bool Launcher::CheckableMessageBox::isChecked() const { return d->checkBox->isChecked(); } -void CheckableMessageBox::setChecked(bool s) +void Launcher::CheckableMessageBox::setChecked(bool s) { d->checkBox->setChecked(s); } -QString CheckableMessageBox::checkBoxText() const +QString Launcher::CheckableMessageBox::checkBoxText() const { return d->checkBox->text(); } -void CheckableMessageBox::setCheckBoxText(const QString &t) +void Launcher::CheckableMessageBox::setCheckBoxText(const QString &t) { d->checkBox->setText(t); } -bool CheckableMessageBox::isCheckBoxVisible() const +bool Launcher::CheckableMessageBox::isCheckBoxVisible() const { return d->checkBox->isVisible(); } -void CheckableMessageBox::setCheckBoxVisible(bool v) +void Launcher::CheckableMessageBox::setCheckBoxVisible(bool v) { d->checkBox->setVisible(v); } -QDialogButtonBox::StandardButtons CheckableMessageBox::standardButtons() const +QDialogButtonBox::StandardButtons Launcher::CheckableMessageBox::standardButtons() const { return d->buttonBox->standardButtons(); } -void CheckableMessageBox::setStandardButtons(QDialogButtonBox::StandardButtons s) +void Launcher::CheckableMessageBox::setStandardButtons(QDialogButtonBox::StandardButtons s) { d->buttonBox->setStandardButtons(s); } -QPushButton *CheckableMessageBox::button(QDialogButtonBox::StandardButton b) const +QPushButton *Launcher::CheckableMessageBox::button(QDialogButtonBox::StandardButton b) const { return d->buttonBox->button(b); } -QPushButton *CheckableMessageBox::addButton(const QString &text, QDialogButtonBox::ButtonRole role) +QPushButton *Launcher::CheckableMessageBox::addButton(const QString &text, QDialogButtonBox::ButtonRole role) { return d->buttonBox->addButton(text, role); } -QDialogButtonBox::StandardButton CheckableMessageBox::defaultButton() const +QDialogButtonBox::StandardButton Launcher::CheckableMessageBox::defaultButton() const { foreach (QAbstractButton *b, d->buttonBox->buttons()) if (QPushButton *pb = qobject_cast(b)) @@ -233,7 +222,7 @@ QDialogButtonBox::StandardButton CheckableMessageBox::defaultButton() const return QDialogButtonBox::NoButton; } -void CheckableMessageBox::setDefaultButton(QDialogButtonBox::StandardButton s) +void Launcher::CheckableMessageBox::setDefaultButton(QDialogButtonBox::StandardButton s) { if (QPushButton *b = d->buttonBox->button(s)) { b->setDefault(true); @@ -242,7 +231,7 @@ void CheckableMessageBox::setDefaultButton(QDialogButtonBox::StandardButton s) } QDialogButtonBox::StandardButton -CheckableMessageBox::question(QWidget *parent, +Launcher::CheckableMessageBox::question(QWidget *parent, const QString &title, const QString &question, const QString &checkBoxText, @@ -263,7 +252,7 @@ CheckableMessageBox::question(QWidget *parent, return mb.clickedStandardButton(); } -QMessageBox::StandardButton CheckableMessageBox::dialogButtonBoxToMessageBoxButton(QDialogButtonBox::StandardButton db) +QMessageBox::StandardButton Launcher::CheckableMessageBox::dialogButtonBoxToMessageBoxButton(QDialogButtonBox::StandardButton db) { return static_cast(int(db)); } diff --git a/apps/launcher/utils/checkablemessagebox.hpp b/apps/launcher/utils/checkablemessagebox.hpp index 93fd43fe1f..09a501b9c2 100644 --- a/apps/launcher/utils/checkablemessagebox.hpp +++ b/apps/launcher/utils/checkablemessagebox.hpp @@ -34,67 +34,83 @@ #include #include -class CheckableMessageBoxPrivate; +class QCheckBox; -class CheckableMessageBox : public QDialog +namespace Launcher { - Q_OBJECT - Q_PROPERTY(QString text READ text WRITE setText) - Q_PROPERTY(QPixmap iconPixmap READ iconPixmap WRITE setIconPixmap) - Q_PROPERTY(bool isChecked READ isChecked WRITE setChecked) - Q_PROPERTY(QString checkBoxText READ checkBoxText WRITE setCheckBoxText) - Q_PROPERTY(QDialogButtonBox::StandardButtons buttons READ standardButtons WRITE setStandardButtons) - Q_PROPERTY(QDialogButtonBox::StandardButton defaultButton READ defaultButton WRITE setDefaultButton) + class CheckableMessageBoxPrivate + { + public: -public: - explicit CheckableMessageBox(QWidget *parent); - virtual ~CheckableMessageBox(); + QLabel *pixmapLabel; + QLabel *messageLabel; + QCheckBox *checkBox; + QDialogButtonBox *buttonBox; + QAbstractButton *clickedButton; - static QDialogButtonBox::StandardButton - question(QWidget *parent, - const QString &title, - const QString &question, - const QString &checkBoxText, - bool *checkBoxSetting, - QDialogButtonBox::StandardButtons buttons = QDialogButtonBox::Yes|QDialogButtonBox::No, - QDialogButtonBox::StandardButton defaultButton = QDialogButtonBox::No); + public: + CheckableMessageBoxPrivate(QDialog *q); + }; - QString text() const; - void setText(const QString &); + class CheckableMessageBox : public QDialog + { + Q_OBJECT + Q_PROPERTY(QString text READ text WRITE setText) + Q_PROPERTY(QPixmap iconPixmap READ iconPixmap WRITE setIconPixmap) + Q_PROPERTY(bool isChecked READ isChecked WRITE setChecked) + Q_PROPERTY(QString checkBoxText READ checkBoxText WRITE setCheckBoxText) + Q_PROPERTY(QDialogButtonBox::StandardButtons buttons READ standardButtons WRITE setStandardButtons) + Q_PROPERTY(QDialogButtonBox::StandardButton defaultButton READ defaultButton WRITE setDefaultButton) - bool isChecked() const; - void setChecked(bool s); + public: + explicit CheckableMessageBox(QWidget *parent); + virtual ~CheckableMessageBox(); - QString checkBoxText() const; - void setCheckBoxText(const QString &); + static QDialogButtonBox::StandardButton + question(QWidget *parent, + const QString &title, + const QString &question, + const QString &checkBoxText, + bool *checkBoxSetting, + QDialogButtonBox::StandardButtons buttons = QDialogButtonBox::Yes|QDialogButtonBox::No, + QDialogButtonBox::StandardButton defaultButton = QDialogButtonBox::No); - bool isCheckBoxVisible() const; - void setCheckBoxVisible(bool); + QString text() const; + void setText(const QString &); - QDialogButtonBox::StandardButtons standardButtons() const; - void setStandardButtons(QDialogButtonBox::StandardButtons s); - QPushButton *button(QDialogButtonBox::StandardButton b) const; - QPushButton *addButton(const QString &text, QDialogButtonBox::ButtonRole role); + bool isChecked() const; + void setChecked(bool s); - QDialogButtonBox::StandardButton defaultButton() const; - void setDefaultButton(QDialogButtonBox::StandardButton s); + QString checkBoxText() const; + void setCheckBoxText(const QString &); - // See static QMessageBox::standardPixmap() - QPixmap iconPixmap() const; - void setIconPixmap (const QPixmap &p); + bool isCheckBoxVisible() const; + void setCheckBoxVisible(bool); - // Query the result - QAbstractButton *clickedButton() const; - QDialogButtonBox::StandardButton clickedStandardButton() const; + QDialogButtonBox::StandardButtons standardButtons() const; + void setStandardButtons(QDialogButtonBox::StandardButtons s); + QPushButton *button(QDialogButtonBox::StandardButton b) const; + QPushButton *addButton(const QString &text, QDialogButtonBox::ButtonRole role); - // Conversion convenience - static QMessageBox::StandardButton dialogButtonBoxToMessageBoxButton(QDialogButtonBox::StandardButton); + QDialogButtonBox::StandardButton defaultButton() const; + void setDefaultButton(QDialogButtonBox::StandardButton s); -private slots: - void slotClicked(QAbstractButton *b); + // See static QMessageBox::standardPixmap() + QPixmap iconPixmap() const; + void setIconPixmap (const QPixmap &p); -private: - CheckableMessageBoxPrivate *d; -}; + // Query the result + QAbstractButton *clickedButton() const; + QDialogButtonBox::StandardButton clickedStandardButton() const; + // Conversion convenience + static QMessageBox::StandardButton dialogButtonBoxToMessageBoxButton(QDialogButtonBox::StandardButton); + + private slots: + void slotClicked(QAbstractButton *b); + + private: + CheckableMessageBoxPrivate *d; + }; +} #endif // CHECKABLEMESSAGEBOX_HPP diff --git a/apps/launcher/utils/textinputdialog.cpp b/apps/launcher/utils/textinputdialog.cpp index 9957e7dda8..76cbe32d01 100644 --- a/apps/launcher/utils/textinputdialog.cpp +++ b/apps/launcher/utils/textinputdialog.cpp @@ -7,7 +7,7 @@ #include #include -TextInputDialog::TextInputDialog(const QString& title, const QString &text, QWidget *parent) : +Launcher::TextInputDialog::TextInputDialog(const QString& title, const QString &text, QWidget *parent) : QDialog(parent) { setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); @@ -45,19 +45,19 @@ TextInputDialog::TextInputDialog(const QString& title, const QString &text, QWid } -int TextInputDialog::exec() +int Launcher::TextInputDialog::exec() { mLineEdit->clear(); mLineEdit->setFocus(); return QDialog::exec(); } -QString TextInputDialog::getText() const +QString Launcher::TextInputDialog::getText() const { return mLineEdit->text(); } -void TextInputDialog::slotUpdateOkButton(QString text) +void Launcher::TextInputDialog::slotUpdateOkButton(QString text) { bool enabled = !(text.isEmpty()); mButtonBox->button(QDialogButtonBox::Ok)->setEnabled(enabled); @@ -73,7 +73,7 @@ void TextInputDialog::slotUpdateOkButton(QString text) } } -TextInputDialog::DialogLineEdit::DialogLineEdit (QWidget *parent) : +Launcher::TextInputDialog::DialogLineEdit::DialogLineEdit (QWidget *parent) : LineEdit (parent) { int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth); diff --git a/apps/launcher/utils/textinputdialog.hpp b/apps/launcher/utils/textinputdialog.hpp index 148bbd1522..bb01778be3 100644 --- a/apps/launcher/utils/textinputdialog.hpp +++ b/apps/launcher/utils/textinputdialog.hpp @@ -7,33 +7,34 @@ class QDialogButtonBox; -class LineEdit; - -class TextInputDialog : public QDialog +namespace Launcher { - Q_OBJECT - - class DialogLineEdit : public LineEdit + class TextInputDialog : public QDialog { + Q_OBJECT + + class DialogLineEdit : public LineEdit + { + public: + explicit DialogLineEdit (QWidget *parent = 0); + }; + + DialogLineEdit *mLineEdit; + QDialogButtonBox *mButtonBox; + public: - explicit DialogLineEdit (QWidget *parent = 0); + + explicit TextInputDialog(const QString& title, const QString &text, QWidget *parent = 0); + ~TextInputDialog () {} + + QString getText() const; + + int exec(); + + private slots: + void slotUpdateOkButton(QString text); + }; - - DialogLineEdit *mLineEdit; - QDialogButtonBox *mButtonBox; - -public: - - explicit TextInputDialog(const QString& title, const QString &text, QWidget *parent = 0); - ~TextInputDialog () {} - - QString getText() const; - - int exec(); - -private slots: - void slotUpdateOkButton(QString text); - -}; +} #endif // TEXTINPUTDIALOG_HPP From 8c9a5de26f2558c64338f4780806788b637510fa Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 25 Oct 2013 23:47:53 +0200 Subject: [PATCH 115/434] Fix an irritating error message --- apps/launcher/graphicspage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index 9308c1d572..97a24d54bd 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -133,7 +133,7 @@ bool GraphicsPage::setupOgre() msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText(tr("
Could not select a valid render system

\ - Please make sure the plugins.cfg file exists and contains a valid rendering plugin.
")); + Please make sure Ogre plugins were installed correctly.
")); msgBox.exec(); return false; } From ba365ff49ed9be07af0244389077c3fc71b36d7a Mon Sep 17 00:00:00 2001 From: graffy76 Date: Fri, 25 Oct 2013 19:23:03 -0500 Subject: [PATCH 116/434] Fixed merge conflicts with saving branch --- apps/launcher/datafilespage.cpp | 34 ------------------- apps/opencs/editor.cpp | 8 ++--- apps/opencs/editor.hpp | 2 +- apps/opencs/model/doc/document.cpp | 2 +- apps/opencs/model/world/idcollection.hpp | 2 +- apps/opencs/view/doc/filedialog.cpp | 42 +++++++++++------------- apps/opencs/view/doc/filedialog.hpp | 9 +++-- 7 files changed, 31 insertions(+), 68 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index d36ab65e93..e246b45154 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -161,42 +161,8 @@ void Launcher::DataFilesPage::slotProfileDeleted (const QString &item) void Launcher::DataFilesPage::slotProfileChangedByUser(const QString &previous, const QString ¤t) { -<<<<<<< HEAD setProfile(previous, current, true); emit signalProfileChanged (ui.profilesComboBox->findText(current)); -======= - if (mContentModel->rowCount() < 1) - return; - - QString profile = mLauncherSettings.value(QString("Profiles/currentprofile")); - - if (profile.isEmpty()) { - profile = profilesComboBox->currentText(); - mLauncherSettings.setValue(QString("Profiles/currentprofile"), profile); - } - - mLauncherSettings.remove(QString("Profiles/") + profile + QString("/master")); - mLauncherSettings.remove(QString("Profiles/") + profile + QString("/plugin")); - - mGameSettings.remove(QString("master")); - mGameSettings.remove(QString("plugins")); - mGameSettings.remove(QString("content")); - - ContentSelectorModel::ContentFileList items = mContentModel->checkedItems(); - - foreach(const ContentSelectorModel::EsmFile *item, items) { - - if (item->gameFiles().size() == 0) { - mLauncherSettings.setMultiValue(QString("Profiles/") + profile + QString("/master"), item->fileName()); - mGameSettings.setMultiValue(QString("content"), item->fileName()); - - } else { - mLauncherSettings.setMultiValue(QString("Profiles/") + profile + QString("/plugin"), item->fileName()); - mGameSettings.setMultiValue(QString("content"), item->fileName()); - } - } - ->>>>>>> 3146af34d642a28b15b55f7eb9999d8ac50168a0 } void Launcher::DataFilesPage::slotProfileRenamed(const QString &previous, const QString ¤t) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 03758b16e5..51cc490c73 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -10,6 +10,7 @@ #include "model/world/data.hpp" #include +#include CS::Editor::Editor() : mDocumentManager (mCfgMgr), mViewManager (mDocumentManager) @@ -19,7 +20,6 @@ CS::Editor::Editor() setupDataFiles(); mNewGame.setLocalData (mLocal); - mFileDialog.setLocalData (mLocal); connect (&mViewManager, SIGNAL (newGameRequest ()), this, SLOT (createGame ())); connect (&mViewManager, SIGNAL (newAddonRequest ()), this, SLOT (createAddon ())); @@ -32,8 +32,8 @@ CS::Editor::Editor() connect (&mStartup, SIGNAL (editConfig()), this, SLOT (showSettings ())); connect (&mFileDialog, SIGNAL(openFiles()), this, SLOT(openFiles())); - connect (&mFileDialog, SIGNAL(createNewFile (const boost::filesystem::path&)), - this, SLOT(createNewFile (const boost::filesystem::path&))); + connect (&mFileDialog, SIGNAL(createNewFile ()), + this, SLOT(createNewFile ())); connect (&mNewGame, SIGNAL (createRequest (const boost::filesystem::path&)), this, SLOT (createNewGame (const boost::filesystem::path&))); @@ -141,7 +141,7 @@ void CS::Editor::openFiles() mFileDialog.hide(); } -void CS::Editor::createNewFile (const boost::filesystem::path& savePath) +void CS::Editor::createNewFile () { std::vector files; diff --git a/apps/opencs/editor.hpp b/apps/opencs/editor.hpp index 16f6b9516c..ef013bc8f9 100644 --- a/apps/opencs/editor.hpp +++ b/apps/opencs/editor.hpp @@ -60,7 +60,7 @@ namespace CS void loadDocument(); void openFiles(); - void createNewFile (const boost::filesystem::path& savePath); + void createNewFile (); void createNewGame (const boost::filesystem::path& file); void showStartup(); diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index cc886f9cc2..590a19439c 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -2246,7 +2246,7 @@ CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, co mData.setAuthor (""); } /// \todo un-outcomment the else, once loading an existing content file works properly again. -// else + else { if (boost::filesystem::exists (mProjectPath)) { diff --git a/apps/opencs/model/world/idcollection.hpp b/apps/opencs/model/world/idcollection.hpp index 0d723bef1e..a7b37be5bc 100644 --- a/apps/opencs/model/world/idcollection.hpp +++ b/apps/opencs/model/world/idcollection.hpp @@ -111,7 +111,7 @@ namespace CSMWorld else { record.mState = RecordBase::State_Deleted; - setRecord (index, record); + this->setRecord (index, record); } return true; diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index 0bf7c6951b..fb6c0a0e89 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -17,8 +17,6 @@ #include "filewidget.hpp" #include "adjusterwidget.hpp" -#include - CSVDoc::FileDialog::FileDialog(QWidget *parent) : QDialog(parent), mSelector (0) { @@ -61,6 +59,13 @@ void CSVDoc::FileDialog::showDialog(DialogType dialogType) break; } + //connections common to both dialog view flavors + connect (mSelector, SIGNAL (signalCurrentGamefileIndexChanged (int)), + this, SLOT (slotUpdateAcceptButton (int))); + + connect (ui.projectButtonBox, SIGNAL (accepted()), this, SIGNAL (createNewFile())); + connect (ui.projectButtonBox, SIGNAL (rejected()), this, SLOT (slotRejected())); + show(); raise(); activateWindow(); @@ -82,13 +87,7 @@ void CSVDoc::FileDialog::buildNewFileView() ui.projectGroupBoxLayout->insertWidget (0, mFileWidget); connect (mFileWidget, SIGNAL (nameChanged(const QString &, bool)), - this, SLOT (slotUpdateCreateButton(const QString &, bool))); - - connect (mSelector, SIGNAL (signalCurrentGamefileIndexChanged (int)), - this, SLOT (slotUpdateCreateButton (int))); - - connect (ui.projectButtonBox, SIGNAL (accepted()), this, SIGNAL (createNewFile())); - connect (ui.projectButtonBox, SIGNAL (rejected()), this, SLOT (slotRejected())); + this, SLOT (slotUpdateAcceptButton(const QString &, bool))); } void CSVDoc::FileDialog::buildOpenFileView() @@ -96,21 +95,25 @@ void CSVDoc::FileDialog::buildOpenFileView() setWindowTitle(tr("Open")); ui.projectGroupBox->setTitle (QString("")); - connect (ui.projectButtonBox, SIGNAL (accepted()), this, SIGNAL (openFiles())); - connect (ui.projectButtonBox, SIGNAL (rejected()), this, SLOT (slotRejected())); + ui.projectButtonBox->button(QDialogButtonBox::Ok)->setEnabled (false); } -void CSVDoc::FileDialog::slotUpdateCreateButton (int) +void CSVDoc::FileDialog::slotUpdateAcceptButton (int) { - slotUpdateCreateButton (mFileWidget->getName(), true); + QString name = ""; + + if (mDialogType == DialogType_New) + name = mFileWidget->getName(); + + slotUpdateAcceptButton (name, true); } -void CSVDoc::FileDialog::slotUpdateCreateButton(const QString &name, bool) +void CSVDoc::FileDialog::slotUpdateAcceptButton(const QString &name, bool) { - if (!(mDialogType == DialogType_New)) - return; + bool success = (mSelector->selectedFiles().size() > 0); - bool success = (!name.isEmpty() && mSelector->selectedFiles().size() > 0); + if (mDialogType == DialogType_New) + success = success && !(name.isEmpty()); ui.projectButtonBox->button (QDialogButtonBox::Ok)->setEnabled (success); } @@ -128,8 +131,3 @@ void CSVDoc::FileDialog::slotRejected() emit rejected(); close(); } - -void CSVDoc::FileDialog::createNewFile() -{ - emit createNewFile (mAdjusterWidget->getPath()); -} diff --git a/apps/opencs/view/doc/filedialog.hpp b/apps/opencs/view/doc/filedialog.hpp index be268f3720..30f2f5d56d 100644 --- a/apps/opencs/view/doc/filedialog.hpp +++ b/apps/opencs/view/doc/filedialog.hpp @@ -55,15 +55,14 @@ namespace CSVDoc signals: void openFiles(); - void createNewFile (const boost::filesystem::path& savePath); + void createNewFile (); - void signalUpdateCreateButton (bool, int); - void signalUpdateCreateButtonFlags(int); + void signalUpdateAcceptButton (bool, int); private slots: - void slotUpdateCreateButton (int); - void slotUpdateCreateButton (const QString &, bool); + void slotUpdateAcceptButton (int); + void slotUpdateAcceptButton (const QString &, bool); void slotRejected(); }; } From 9b483c3ae31f0ce11ea9a47fbada16441cf2c4ae Mon Sep 17 00:00:00 2001 From: graffy76 Date: Sat, 26 Oct 2013 22:55:44 -0500 Subject: [PATCH 117/434] Fix for file path issues --- apps/opencs/editor.cpp | 20 +++++++-------- apps/opencs/editor.hpp | 4 +-- apps/opencs/view/doc/filedialog.cpp | 39 ++++++++++++++++++++++++++--- apps/opencs/view/doc/filedialog.hpp | 17 +++++++++++-- files/ui/filedialog.ui | 4 +-- 5 files changed, 64 insertions(+), 20 deletions(-) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 51cc490c73..a8006d2b41 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -20,6 +20,7 @@ CS::Editor::Editor() setupDataFiles(); mNewGame.setLocalData (mLocal); + mFileDialog.setLocalData (mLocal); connect (&mViewManager, SIGNAL (newGameRequest ()), this, SLOT (createGame ())); connect (&mViewManager, SIGNAL (newAddonRequest ()), this, SLOT (createAddon ())); @@ -31,9 +32,11 @@ CS::Editor::Editor() connect (&mStartup, SIGNAL (loadDocument()), this, SLOT (loadDocument ())); connect (&mStartup, SIGNAL (editConfig()), this, SLOT (showSettings ())); - connect (&mFileDialog, SIGNAL(openFiles()), this, SLOT(openFiles())); - connect (&mFileDialog, SIGNAL(createNewFile ()), - this, SLOT(createNewFile ())); + connect (&mFileDialog, SIGNAL(signalOpenFiles (const boost::filesystem::path&)), + this, SLOT(openFiles (const boost::filesystem::path&))); + + connect (&mFileDialog, SIGNAL(signalCreateNewFile (const boost::filesystem::path&)), + this, SLOT(createNewFile (const boost::filesystem::path&))); connect (&mNewGame, SIGNAL (createRequest (const boost::filesystem::path&)), this, SLOT (createNewGame (const boost::filesystem::path&))); @@ -125,15 +128,12 @@ void CS::Editor::loadDocument() mFileDialog.showDialog (CSVDoc::FileDialog::DialogType_Open); } -void CS::Editor::openFiles() +void CS::Editor::openFiles (const boost::filesystem::path &savePath) { std::vector files; - foreach (const QString &path, mFileDialog.selectedFilePaths()) { + foreach (const QString &path, mFileDialog.selectedFilePaths()) files.push_back(path.toStdString()); - } - - boost::filesystem::path savePath = mFileDialog.filename().toStdString(); CSMDoc::Document *document = mDocumentManager.addDocument (files, savePath, false); @@ -141,7 +141,7 @@ void CS::Editor::openFiles() mFileDialog.hide(); } -void CS::Editor::createNewFile () +void CS::Editor::createNewFile (const boost::filesystem::path &savePath) { std::vector files; @@ -151,8 +151,6 @@ void CS::Editor::createNewFile () files.push_back(mFileDialog.filename().toStdString()); - boost::filesystem::path savePath = mFileDialog.filename().toStdString(); - CSMDoc::Document *document = mDocumentManager.addDocument (files, savePath, true); mViewManager.addView (document); diff --git a/apps/opencs/editor.hpp b/apps/opencs/editor.hpp index ef013bc8f9..930aa9d643 100644 --- a/apps/opencs/editor.hpp +++ b/apps/opencs/editor.hpp @@ -59,8 +59,8 @@ namespace CS void createAddon(); void loadDocument(); - void openFiles(); - void createNewFile (); + void openFiles (const boost::filesystem::path &path); + void createNewFile (const boost::filesystem::path& path); void createNewGame (const boost::filesystem::path& file); void showStartup(); diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index fb6c0a0e89..eb94aa5f4f 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -17,14 +17,17 @@ #include "filewidget.hpp" #include "adjusterwidget.hpp" +#include + CSVDoc::FileDialog::FileDialog(QWidget *parent) : - QDialog(parent), mSelector (0) + QDialog(parent), mSelector (0), mFileWidget (0), mAdjusterWidget (0) { ui.setupUi (this); resize(400, 400); setObjectName ("FileDialog"); mSelector = new ContentSelectorView::ContentSelector (ui.contentSelectorWidget); + mAdjusterWidget = new AdjusterWidget (this); } void CSVDoc::FileDialog::addFiles(const QString &path) @@ -42,6 +45,14 @@ QStringList CSVDoc::FileDialog::selectedFilePaths() return filePaths; } +void CSVDoc::FileDialog::setLocalData (const boost::filesystem::path& localData) +{ + if (mDialogType != DialogType_New) + return; + + mAdjusterWidget->setLocalData (localData); +} + void CSVDoc::FileDialog::showDialog(DialogType dialogType) { mDialogType = dialogType; @@ -63,7 +74,6 @@ void CSVDoc::FileDialog::showDialog(DialogType dialogType) connect (mSelector, SIGNAL (signalCurrentGamefileIndexChanged (int)), this, SLOT (slotUpdateAcceptButton (int))); - connect (ui.projectButtonBox, SIGNAL (accepted()), this, SIGNAL (createNewFile())); connect (ui.projectButtonBox, SIGNAL (rejected()), this, SLOT (slotRejected())); show(); @@ -85,17 +95,26 @@ void CSVDoc::FileDialog::buildNewFileView() mFileWidget->extensionLabelIsVisible(true); ui.projectGroupBoxLayout->insertWidget (0, mFileWidget); + ui.projectGroupBoxLayout->insertWidget (1, mAdjusterWidget); + + connect (mFileWidget, SIGNAL (nameChanged (const QString&, bool)), + mAdjusterWidget, SLOT (setName (const QString&, bool))); connect (mFileWidget, SIGNAL (nameChanged(const QString &, bool)), this, SLOT (slotUpdateAcceptButton(const QString &, bool))); + + connect (ui.projectButtonBox, SIGNAL (accepted()), this, SLOT (slotNewFile())); } void CSVDoc::FileDialog::buildOpenFileView() { setWindowTitle(tr("Open")); ui.projectGroupBox->setTitle (QString("")); + ui.projectGroupBox->layout()->addWidget (mAdjusterWidget); ui.projectButtonBox->button(QDialogButtonBox::Ok)->setEnabled (false); + + connect (ui.projectButtonBox, SIGNAL (accepted()), this, SLOT (slotOpenFile())); } void CSVDoc::FileDialog::slotUpdateAcceptButton (int) @@ -121,7 +140,7 @@ void CSVDoc::FileDialog::slotUpdateAcceptButton(const QString &name, bool) QString CSVDoc::FileDialog::filename() const { if (mDialogType == DialogType_New) - return mFileWidget->getName(); + return ""; return mSelector->currentFile(); } @@ -131,3 +150,17 @@ void CSVDoc::FileDialog::slotRejected() emit rejected(); close(); } + +void CSVDoc::FileDialog::slotNewFile() +{ + emit signalCreateNewFile (mAdjusterWidget->getPath()); +} + +void CSVDoc::FileDialog::slotOpenFile() +{ + ContentSelectorModel::EsmFile *file = mSelector->selectedFiles().back(); + + mAdjusterWidget->setName (file->fileName(), !file->isGameFile()); + + emit signalOpenFiles (mAdjusterWidget->getPath()); +} diff --git a/apps/opencs/view/doc/filedialog.hpp b/apps/opencs/view/doc/filedialog.hpp index 30f2f5d56d..777ee31e4c 100644 --- a/apps/opencs/view/doc/filedialog.hpp +++ b/apps/opencs/view/doc/filedialog.hpp @@ -4,6 +4,13 @@ #include #include +#include + +#ifndef CS_QT_BOOST_FILESYSTEM_PATH_DECLARED +#define CS_QT_BOOST_FILESYSTEM_PATH_DECLARED +Q_DECLARE_METATYPE (boost::filesystem::path) +#endif + #include "ui_filedialog.h" class DataFilesModel; @@ -17,6 +24,7 @@ namespace ContentSelectorView namespace CSVDoc { class FileWidget; + class AdjusterWidget; class FileDialog : public QDialog { @@ -36,6 +44,7 @@ namespace CSVDoc Ui::FileDialog ui; DialogType mDialogType; FileWidget *mFileWidget; + AdjusterWidget *mAdjusterWidget; public: @@ -47,6 +56,8 @@ namespace CSVDoc QString filename() const; QStringList selectedFilePaths(); + void setLocalData (const boost::filesystem::path& localData); + private: void buildNewFileView(); @@ -54,13 +65,15 @@ namespace CSVDoc signals: - void openFiles(); - void createNewFile (); + void signalOpenFiles (const boost::filesystem::path &path); + void signalCreateNewFile (const boost::filesystem::path &path); void signalUpdateAcceptButton (bool, int); private slots: + void slotNewFile(); + void slotOpenFile(); void slotUpdateAcceptButton (int); void slotUpdateAcceptButton (const QString &, bool); void slotRejected(); diff --git a/files/ui/filedialog.ui b/files/ui/filedialog.ui index 114345e53b..b3af166dab 100644 --- a/files/ui/filedialog.ui +++ b/files/ui/filedialog.ui @@ -7,7 +7,7 @@ 0 0 518 - 108 + 109
@@ -52,7 +52,7 @@ - + From 5e123d3f52bafc42d63455f91d30d7b2677a9389 Mon Sep 17 00:00:00 2001 From: graffy76 Date: Sat, 26 Oct 2013 22:57:22 -0500 Subject: [PATCH 118/434] Hide adjusterwidget for open files view --- apps/opencs/view/doc/filedialog.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index eb94aa5f4f..903e78cf16 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -112,6 +112,8 @@ void CSVDoc::FileDialog::buildOpenFileView() ui.projectGroupBox->setTitle (QString("")); ui.projectGroupBox->layout()->addWidget (mAdjusterWidget); + mAdjusterWidget->setVisible (false); + ui.projectButtonBox->button(QDialogButtonBox::Ok)->setEnabled (false); connect (ui.projectButtonBox, SIGNAL (accepted()), this, SLOT (slotOpenFile())); From ea7a8eb2a4e7367530831c8172e159d6a9acf663 Mon Sep 17 00:00:00 2001 From: graffy76 Date: Sat, 26 Oct 2013 23:04:39 -0500 Subject: [PATCH 119/434] last commit --- apps/opencs/editor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index a8006d2b41..1c861ed67e 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -135,6 +135,7 @@ void CS::Editor::openFiles (const boost::filesystem::path &savePath) foreach (const QString &path, mFileDialog.selectedFilePaths()) files.push_back(path.toStdString()); + qDebug() << "save file path: " << savePath.c_str(); CSMDoc::Document *document = mDocumentManager.addDocument (files, savePath, false); mViewManager.addView (document); From 23eaf90846125869bb5fd93fe84d7209995f45b2 Mon Sep 17 00:00:00 2001 From: "Alex \"rainChu\" Haddad" Date: Sun, 27 Oct 2013 04:05:01 -0400 Subject: [PATCH 120/434] Breath meter flashes when drowning --- apps/openmw/mwgui/hud.cpp | 22 +++++++++++++++++++++- apps/openmw/mwgui/hud.hpp | 5 ++++- files/mygui/openmw_hud.layout | 1 + files/mygui/openmw_progress.skin.xml | 9 +++++++++ 4 files changed, 35 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index edcd49738e..e7b9f9c015 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -28,6 +28,7 @@ namespace MWGui , mStamina(NULL) , mDrowning(NULL) , mDrowningFrame(NULL) + , mDrowningFlash(NULL) , mWeapImage(NULL) , mSpellImage(NULL) , mWeapStatus(NULL) @@ -53,6 +54,7 @@ namespace MWGui , mSpellVisible(true) , mWorldMouseOver(false) , mEnemyHealthTimer(0) + , mIsDrowning(false) { setCoord(0,0, width, height); @@ -75,6 +77,7 @@ namespace MWGui //Drowning bar getWidget(mDrowningFrame, "DrowningFrame"); getWidget(mDrowning, "Drowning"); + getWidget(mDrowningFlash, "Flash"); mDrowning->setProgressRange(200); const MyGUI::IntSize& viewSize = MyGUI::RenderManager::getInstance().getViewSize(); @@ -207,7 +210,15 @@ namespace MWGui void HUD::setDrowningTimeLeft(float time) { - mDrowning->setProgressPosition(time/20.0*200.0); + size_t progress = time/20.0*200.0; + mDrowning->setProgressPosition(progress); + + bool isDrowning = (progress == 0); + if (isDrowning && !mIsDrowning) // Just started drowning + mDrowningFlashTheta = 0.0f; // Start out on bright red every time. + + mDrowningFlash->setVisible(isDrowning); + mIsDrowning = isDrowning; } void HUD::setDrowningBarVisible(bool visible) @@ -367,6 +378,9 @@ namespace MWGui mEnemyHealth->setVisible(false); mWeaponSpellBox->setPosition(mWeaponSpellBox->getPosition() + MyGUI::IntPoint(0,20)); } + + if (mIsDrowning) + mDrowningFlashTheta += dt * Ogre::Math::TWO_PI; } void HUD::onResChange(int width, int height) @@ -609,6 +623,12 @@ namespace MWGui mEnemyHealth->setProgressRange(100); mEnemyHealth->setProgressPosition(stats.getHealth().getCurrent() / stats.getHealth().getModified() * 100); } + + if (mIsDrowning) + { + float intensity = (cos(mDrowningFlashTheta) + 1.0f) / 2.0f; + mDrowningFlash->setColour(MyGUI::Colour(intensity, intensity, intensity)); + } } void HUD::setEnemy(const MWWorld::Ptr &enemy) diff --git a/apps/openmw/mwgui/hud.hpp b/apps/openmw/mwgui/hud.hpp index c40742a603..04206fbc88 100644 --- a/apps/openmw/mwgui/hud.hpp +++ b/apps/openmw/mwgui/hud.hpp @@ -67,7 +67,7 @@ namespace MWGui MyGUI::ImageBox* mCrosshair; MyGUI::TextBox* mCellNameBox; MyGUI::TextBox* mWeaponSpellBox; - MyGUI::Widget* mDrowningFrame; + MyGUI::Widget *mDrowningFrame, *mDrowningFlash; MyGUI::Widget* mDummy; @@ -101,6 +101,9 @@ namespace MWGui MWWorld::Ptr mEnemy; float mEnemyHealthTimer; + bool mIsDrowning; + float mDrowningFlashTheta; + void onWorldClicked(MyGUI::Widget* _sender); void onWorldMouseOver(MyGUI::Widget* _sender, int x, int y); void onWorldMouseLostFocus(MyGUI::Widget* _sender, MyGUI::Widget* _new); diff --git a/files/mygui/openmw_hud.layout b/files/mygui/openmw_hud.layout index e39777dd0f..72d337e45d 100644 --- a/files/mygui/openmw_hud.layout +++ b/files/mygui/openmw_hud.layout @@ -45,6 +45,7 @@ + diff --git a/files/mygui/openmw_progress.skin.xml b/files/mygui/openmw_progress.skin.xml index 35114ffebb..f5418e3f88 100644 --- a/files/mygui/openmw_progress.skin.xml +++ b/files/mygui/openmw_progress.skin.xml @@ -17,6 +17,11 @@ + + + + + @@ -65,6 +70,10 @@ + + + + From 882b136b35736eb78fbcce63c7e2e16ccdb85f9a Mon Sep 17 00:00:00 2001 From: gus Date: Sun, 27 Oct 2013 13:21:16 +0100 Subject: [PATCH 121/434] bugfix of bugfix ^^ --- apps/openmw/mwmechanics/aisequence.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index ecd21d04c3..5e1bc17910 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -23,7 +23,8 @@ void MWMechanics::AiSequence::copy (const AiSequence& sequence) iter!=sequence.mPackages.end(); ++iter) mPackages.push_back ((*iter)->clone()); mCombat = sequence.mCombat; - mCombatPackage = sequence.mCombatPackage->clone(); + mCombatPackage = 0; + if(sequence.mCombat) mCombatPackage = sequence.mCombatPackage->clone(); } MWMechanics::AiSequence::AiSequence() : mDone (false), mCombat (false), mCombatPackage (0) {} From a0edb55f600844b7f5474df1395f7476983d7e99 Mon Sep 17 00:00:00 2001 From: gus Date: Sun, 27 Oct 2013 13:50:29 +0100 Subject: [PATCH 122/434] only NPC with fight over 80 will attack you now --- apps/openmw/mwmechanics/aisequence.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 5e1bc17910..86c07947f6 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -73,8 +73,11 @@ void MWMechanics::AiSequence::execute (const MWWorld::Ptr& actor) } else { - mCombat = true; - mCombatPackage = new AiCombat("player"); + if(actor.getClass().getCreatureStats(actor).getAiSetting(1)> 80) + { + mCombat = true; + mCombatPackage = new AiCombat("player"); + } if (!mPackages.empty()) { if (mPackages.front()->execute (actor)) From 525d6fadece204ce6b844b5b70488f6a6ca8e0f5 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 27 Oct 2013 14:00:25 +0100 Subject: [PATCH 123/434] added Collection insert function for arbitrary position and reimplemented appendRecord in via this function --- apps/opencs/model/world/collection.hpp | 37 +++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/apps/opencs/model/world/collection.hpp b/apps/opencs/model/world/collection.hpp index 5ae53e7101..4d93dc8810 100644 --- a/apps/opencs/model/world/collection.hpp +++ b/apps/opencs/model/world/collection.hpp @@ -113,6 +113,15 @@ namespace CSMWorld /// /// \param listDeleted include deleted record in the list + virtual void insertRecord (const RecordBase& record, int index, + UniversalId::Type type = UniversalId::Type_None); + ///< Insert record before index. + /// + /// If the record type does not match, an exception is thrown. + /// + /// If the index is invalid either generally (by being out of range) or for the particular + /// record, an exception is thrown. + void addColumn (Column *column); void setRecord (int index, const Record& record); @@ -287,10 +296,7 @@ namespace CSMWorld void Collection::appendRecord (const RecordBase& record, UniversalId::Type type) { - mRecords.push_back (dynamic_cast&> (record)); - mIndex.insert (std::make_pair (Misc::StringUtils::lowerCase (IdAccessorT().getId ( - dynamic_cast&> (record).get())), - mRecords.size()-1)); + insertRecord (record, mRecords.size(), type); } template @@ -328,6 +334,29 @@ namespace CSMWorld return mRecords.at (index); } + template + void Collection::insertRecord (const RecordBase& record, int index, + UniversalId::Type type) + { + if (index<0 || index>static_cast (mRecords.size())) + throw std::runtime_error ("index out of range"); + + const Record& record2 = dynamic_cast&> (record); + + mRecords.insert (mRecords.begin()+index, record2); + + if (index (mRecords.size())-1) + { + for (std::map::iterator iter (mIndex.begin()); iter!=mIndex.end(); + ++iter) + if (iter->second>=index) + ++(iter->second); + } + + mIndex.insert (std::make_pair (Misc::StringUtils::lowerCase (IdAccessorT().getId ( + record2.get())), index)); + } + template void Collection::setRecord (int index, const Record& record) { From 968968502e623857ae730a2a386cbc64108c1066 Mon Sep 17 00:00:00 2001 From: gus Date: Sun, 27 Oct 2013 14:03:58 +0100 Subject: [PATCH 124/434] NPC always face you when fighting --- apps/openmw/mwmechanics/aicombat.cpp | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 529855b2ff..8f51c462fa 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -9,6 +9,17 @@ #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "OgreMath.h" + +namespace +{ + static float sgn(float a) + { + if(a > 0) + return 1.0; + return -1.0; + } +} namespace MWMechanics { @@ -71,12 +82,20 @@ namespace MWMechanics float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false); MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; + float range = 100; - + MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(false); if((dest.mX - start.mX)*(dest.mX - start.mX)+(dest.mY - start.mY)*(dest.mY - start.mY)+(dest.mZ - start.mZ)*(dest.mZ - start.mZ) < range*range) { + float directionX = dest.mX - start.mX; + float directionY = dest.mY - start.mY; + float directionResult = sqrt(directionX * directionX + directionY * directionY); + + zAngle = Ogre::Radian( acos(directionY / directionResult) * sgn(asin(directionX / directionResult)) ).valueDegrees(); + MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false); + mPathFinder.clearPath(); MWWorld::TimeStamp time = MWBase::Environment::get().getWorld()->getTimeStamp(); From 9a80e111828457af1ed93f6c7294983c4ea301b0 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 27 Oct 2013 14:13:10 +0100 Subject: [PATCH 125/434] reimplemented add and appendBlankRecord via insertRecord --- apps/opencs/model/world/collection.hpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/opencs/model/world/collection.hpp b/apps/opencs/model/world/collection.hpp index 4d93dc8810..167210de93 100644 --- a/apps/opencs/model/world/collection.hpp +++ b/apps/opencs/model/world/collection.hpp @@ -152,8 +152,7 @@ namespace CSMWorld record2.mState = Record::State_ModifiedOnly; record2.mModified = record; - mRecords.push_back (record2); - mIndex.insert (std::make_pair (Misc::StringUtils::lowerCase (id), mRecords.size()-1)); + insertRecord (record2, mRecords.size()); } else { @@ -270,7 +269,12 @@ namespace CSMWorld ESXRecordT record; IdAccessorT().getId (record) = id; record.blank(); - add (record); + + Record record2; + record2.mState = Record::State_ModifiedOnly; + record2.mModified = record; + + insertRecord (record2, mRecords.size(), type); } template From 2515a1239e67b169f0e1d3a189f6eb25f18a18cd Mon Sep 17 00:00:00 2001 From: gus Date: Sun, 27 Oct 2013 14:22:51 +0100 Subject: [PATCH 126/434] improved combat conditions --- apps/openmw/mwmechanics/aisequence.cpp | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 86c07947f6..a798040fe6 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -17,6 +17,8 @@ #include "..\mwbase\world.hpp" #include "..\mwworld\player.hpp" +#include "..\mwbase\mechanicsmanager.hpp" + void MWMechanics::AiSequence::copy (const AiSequence& sequence) { for (std::list::const_iterator iter (sequence.mPackages.begin()); @@ -73,7 +75,23 @@ void MWMechanics::AiSequence::execute (const MWWorld::Ptr& actor) } else { - if(actor.getClass().getCreatureStats(actor).getAiSetting(1)> 80) + ESM::Position playerpos = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getRefData().getPosition(); + ESM::Position actorpos = actor.getRefData().getPosition(); + float d = sqrt((actorpos.pos[0] - playerpos.pos[0])*(actorpos.pos[0] - playerpos.pos[0]) + +(actorpos.pos[1] - playerpos.pos[1])*(actorpos.pos[1] - playerpos.pos[1]) + +(actorpos.pos[2] - playerpos.pos[2])*(actorpos.pos[2] - playerpos.pos[2])); + float fight = actor.getClass().getCreatureStats(actor).getAiSetting(1); + float disp = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(actor); + if(fight == 100 + || (fight >= 95 && d <= 3000) + || (fight >= 90 && d <= 2000) + || (fight >= 80 && d <= 1000) + || (fight >= 80 && disp <= 40) + || (fight >= 70 && disp <= 35 && d <= 1000) + || (fight >= 60 && disp <= 30 && d <= 1000) + || (fight >= 50 && disp == 0) + || (fight >= 40 && disp <= 10 && d <= 500) + ) { mCombat = true; mCombatPackage = new AiCombat("player"); From 489475a32f14e24e4554b393261280abc947c194 Mon Sep 17 00:00:00 2001 From: Lukasz Gromanowski Date: Sun, 27 Oct 2013 19:57:05 +0100 Subject: [PATCH 127/434] Corrected compilation error (g++ 4.8.2) triggered by not found declaration in argument-dependent lookup at the point of instantiation. Signed-off-by: Lukasz Gromanowski --- apps/opencs/model/world/idcollection.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/model/world/idcollection.hpp b/apps/opencs/model/world/idcollection.hpp index 0d723bef1e..a7b37be5bc 100644 --- a/apps/opencs/model/world/idcollection.hpp +++ b/apps/opencs/model/world/idcollection.hpp @@ -111,7 +111,7 @@ namespace CSMWorld else { record.mState = RecordBase::State_Deleted; - setRecord (index, record); + this->setRecord (index, record); } return true; From d51c9b64dd2c4df61b03ce5dadfb724bcb6dc07f Mon Sep 17 00:00:00 2001 From: Lukasz Gromanowski Date: Sun, 27 Oct 2013 20:03:12 +0100 Subject: [PATCH 128/434] Issue #913: Merge --master and --plugin switches Launcher part of master/plugin switches merge. Signed-off-by: Lukasz Gromanowski --- apps/launcher/datafilespage.cpp | 28 +++-------------- apps/launcher/maindialog.cpp | 16 +++------- apps/launcher/settings/gamesettings.cpp | 31 ++++++++++++------- apps/launcher/settings/gamesettings.hpp | 10 ++++-- .../contentselector/model/contentmodel.cpp | 10 +++++- 5 files changed, 45 insertions(+), 50 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 43f09d1687..5e19ba9c9d 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -185,9 +185,6 @@ void DataFilesPage::loadSettings() // mContentSelector. mContentModel->uncheckAll(); - - QStringList gameFiles = mLauncherSettings.values(QString("Profiles/") + profile + QString("/master"), Qt::MatchExactly); - QStringList addons = mLauncherSettings.values(QString("Profiles/") + profile + QString("/plugin"), Qt::MatchExactly); } void DataFilesPage::saveSettings() @@ -202,27 +199,14 @@ void DataFilesPage::saveSettings() mLauncherSettings.setValue(QString("Profiles/currentprofile"), profile); } - mLauncherSettings.remove(QString("Profiles/") + profile + QString("/master")); - mLauncherSettings.remove(QString("Profiles/") + profile + QString("/plugin")); - - mGameSettings.remove(QString("master")); - mGameSettings.remove(QString("plugins")); + mLauncherSettings.remove(QString("Profiles/") + profile + QString("/content")); mGameSettings.remove(QString("content")); ContentSelectorModel::ContentFileList items = mContentModel->checkedItems(); - foreach(const ContentSelectorModel::EsmFile *item, items) { - - if (item->gameFiles().size() == 0) { - mLauncherSettings.setMultiValue(QString("Profiles/") + profile + QString("/master"), item->fileName()); - mGameSettings.setMultiValue(QString("content"), item->fileName()); - - } else { - mLauncherSettings.setMultiValue(QString("Profiles/") + profile + QString("/plugin"), item->fileName()); - mGameSettings.setMultiValue(QString("content"), item->fileName()); - } + mLauncherSettings.setMultiValue(QString("Profiles/") + profile + QString("/content"), item->fileName()); + mGameSettings.setMultiValue(QString("content"), item->fileName()); } - } void DataFilesPage::updateOkButton(const QString &text) @@ -281,8 +265,7 @@ void DataFilesPage::on_deleteProfileAction_triggered() msgBox.exec(); if (msgBox.clickedButton() == deleteButton) { - mLauncherSettings.remove(QString("Profiles/") + profile + QString("/master")); - mLauncherSettings.remove(QString("Profiles/") + profile + QString("/plugin")); + mLauncherSettings.remove(QString("Profiles/") + profile + QString("/content")); // Remove the profile from the combobox profilesComboBox->removeItem(profilesComboBox->findText(profile)); @@ -348,8 +331,7 @@ void DataFilesPage::profileRenamed(const QString &previous, const QString &curre saveSettings(); // Remove the old one - mLauncherSettings.remove(QString("Profiles/") + previous + QString("/master")); - mLauncherSettings.remove(QString("Profiles/") + previous + QString("/plugin")); + mLauncherSettings.remove(QString("Profiles/") + previous + QString("/content")); // Remove the profile from the combobox profilesComboBox->removeItem(profilesComboBox->findText(previous)); diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 032f70916f..0f59b3aaf0 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -261,19 +261,11 @@ bool MainDialog::showFirstRunDialog() // Add a new profile if (msgBox.isChecked()) { mLauncherSettings.setValue(QString("Profiles/currentprofile"), QString("Imported")); + mLauncherSettings.remove(QString("Profiles/Imported/content")); - mLauncherSettings.remove(QString("Profiles/Imported/master")); - mLauncherSettings.remove(QString("Profiles/Imported/plugin")); - - QStringList masters = mGameSettings.values(QString("master")); - QStringList plugins = mGameSettings.values(QString("plugin")); - - foreach (const QString &master, masters) { - mLauncherSettings.setMultiValue(QString("Profiles/Imported/master"), master); - } - - foreach (const QString &plugin, plugins) { - mLauncherSettings.setMultiValue(QString("Profiles/Imported/plugin"), plugin); + QStringList contents = mGameSettings.values(QString("content")); + foreach (const QString &content, contents) { + mLauncherSettings.setMultiValue(QString("Profiles/Imported/content"), content); } } diff --git a/apps/launcher/settings/gamesettings.cpp b/apps/launcher/settings/gamesettings.cpp index 7b2356cd08..211e319279 100644 --- a/apps/launcher/settings/gamesettings.cpp +++ b/apps/launcher/settings/gamesettings.cpp @@ -139,13 +139,13 @@ bool GameSettings::writeFile(QTextStream &stream) while (i.hasPrevious()) { i.previous(); - if (i.key() == QLatin1String("master") || i.key() == QLatin1String("plugin")) + if (i.key() == QLatin1String("content")) continue; // Quote paths with spaces if (i.key() == QLatin1String("data") - || i.key() == QLatin1String("data-local") - || i.key() == QLatin1String("resources")) + || i.key() == QLatin1String("data-local") + || i.key() == QLatin1String("resources")) { if (i.value().contains(QChar(' '))) { @@ -161,15 +161,24 @@ bool GameSettings::writeFile(QTextStream &stream) } - QStringList masters = mSettings.values(QString("master")); - for (int i = masters.count(); i--;) { - stream << "content=" << masters.at(i) << "\n"; - } - - QStringList plugins = mSettings.values(QString("plugin")); - for (int i = plugins.count(); i--;) { - stream << "content=" << plugins.at(i) << "\n"; + QStringList content = mSettings.values(QString("content")); + for (int i = content.count(); i--;) { + stream << "content=" << content.at(i) << "\n"; } return true; } + +bool GameSettings::hasMaster() +{ + bool result = false; + QStringList content = mSettings.values(QString("content")); + for (int i = 0; i < content.count(); ++i) { + if (content.at(i).contains(".omwgame") || content.at(i).contains(".esm")) { + result = true; + break; + } + } + + return result; +} diff --git a/apps/launcher/settings/gamesettings.hpp b/apps/launcher/settings/gamesettings.hpp index 55b2107e2a..c009c30ed9 100644 --- a/apps/launcher/settings/gamesettings.hpp +++ b/apps/launcher/settings/gamesettings.hpp @@ -8,8 +8,11 @@ #include -namespace Files { typedef std::vector PathContainer; - struct ConfigurationManager;} +namespace Files +{ + typedef std::vector PathContainer; + struct ConfigurationManager; +} class GameSettings { @@ -43,7 +46,8 @@ public: inline QStringList getDataDirs() { return mDataDirs; } inline void addDataDir(const QString &dir) { if(!dir.isEmpty()) mDataDirs.append(dir); } inline QString getDataLocal() {return mDataLocal; } - inline bool hasMaster() { return mSettings.count(QString("master")) > 0; } + + bool hasMaster(); QStringList values(const QString &key, const QStringList &defaultValues = QStringList()); bool readFile(QTextStream &stream); diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index b85da25c67..fb011198ca 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -461,9 +461,17 @@ ContentSelectorModel::ContentFileList ContentSelectorModel::ContentModel::checke { ContentFileList list; + // First search for game files and next addons, + // so we get more or less correct game files vs addons order. foreach (EsmFile *file, mFiles) { - if (isChecked(file->fileName())) + if (isChecked(file->fileName()) && file->isGameFile()) + list << file; + } + + foreach (EsmFile *file, mFiles) + { + if (isChecked(file->fileName()) && !file->isGameFile()) list << file; } From b51bef0d98687c95d35f69a8b02b5b2fac6d48a5 Mon Sep 17 00:00:00 2001 From: graffy76 Date: Sun, 27 Oct 2013 20:21:19 -0500 Subject: [PATCH 129/434] fixed missing adjuster widget in file dialog open view --- apps/opencs/view/doc/adjusterwidget.cpp | 13 +++++++++- apps/opencs/view/doc/adjusterwidget.hpp | 11 +++++++++ apps/opencs/view/doc/filedialog.cpp | 24 ++++++++++++------- apps/opencs/view/doc/filedialog.hpp | 14 +++-------- .../contentselector/view/contentselector.cpp | 12 ++++++++-- .../contentselector/view/contentselector.hpp | 2 ++ 6 files changed, 53 insertions(+), 23 deletions(-) diff --git a/apps/opencs/view/doc/adjusterwidget.cpp b/apps/opencs/view/doc/adjusterwidget.cpp index b1eec63c37..332d460758 100644 --- a/apps/opencs/view/doc/adjusterwidget.cpp +++ b/apps/opencs/view/doc/adjusterwidget.cpp @@ -11,7 +11,7 @@ #include CSVDoc::AdjusterWidget::AdjusterWidget (QWidget *parent) -: QWidget (parent), mValid (false) + : QWidget (parent), mValid (false), mAction (ContentAction_Undefined) { QHBoxLayout *layout = new QHBoxLayout (this); @@ -30,6 +30,11 @@ CSVDoc::AdjusterWidget::AdjusterWidget (QWidget *parent) setLayout (layout); } +void CSVDoc::AdjusterWidget::setAction (ContentAction action) +{ + mAction = action; +} + void CSVDoc::AdjusterWidget::setLocalData (const boost::filesystem::path& localData) { mLocalData = localData; @@ -51,6 +56,12 @@ void CSVDoc::AdjusterWidget::setName (const QString& name, bool addon) { QString message; + if (mAction == ContentAction_Undefined) + { + throw std::runtime_error("ContentAction_Undefined when AdjusterWidget::setName() called."); + return; + } + if (name.isEmpty()) { mValid = false; diff --git a/apps/opencs/view/doc/adjusterwidget.hpp b/apps/opencs/view/doc/adjusterwidget.hpp index 461cfb3452..627f89c1f8 100644 --- a/apps/opencs/view/doc/adjusterwidget.hpp +++ b/apps/opencs/view/doc/adjusterwidget.hpp @@ -9,21 +9,32 @@ class QLabel; namespace CSVDoc { + enum ContentAction + { + ContentAction_New, + ContentAction_Edit, + ContentAction_Undefined + }; + class AdjusterWidget : public QWidget { Q_OBJECT + public: + boost::filesystem::path mLocalData; QLabel *mMessage; QLabel *mIcon; bool mValid; boost::filesystem::path mResultPath; + ContentAction mAction; public: AdjusterWidget (QWidget *parent = 0); void setLocalData (const boost::filesystem::path& localData); + void setAction (ContentAction action); bool isValid() const; diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index 903e78cf16..57b61ff8b2 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -53,17 +53,19 @@ void CSVDoc::FileDialog::setLocalData (const boost::filesystem::path& localData) mAdjusterWidget->setLocalData (localData); } -void CSVDoc::FileDialog::showDialog(DialogType dialogType) +void CSVDoc::FileDialog::showDialog (ContentAction action) { - mDialogType = dialogType; + mAction = action; - switch (mDialogType) + ui.projectGroupBoxLayout->insertWidget (0, mAdjusterWidget); + + switch (mAction) { - case DialogType_New: + case ContentAction_New: buildNewFileView(); break; - case DialogType_Open: + case ContentAction_Edit: buildOpenFileView(); break; default: @@ -95,7 +97,6 @@ void CSVDoc::FileDialog::buildNewFileView() mFileWidget->extensionLabelIsVisible(true); ui.projectGroupBoxLayout->insertWidget (0, mFileWidget); - ui.projectGroupBoxLayout->insertWidget (1, mAdjusterWidget); connect (mFileWidget, SIGNAL (nameChanged (const QString&, bool)), mAdjusterWidget, SLOT (setName (const QString&, bool))); @@ -110,12 +111,12 @@ void CSVDoc::FileDialog::buildOpenFileView() { setWindowTitle(tr("Open")); ui.projectGroupBox->setTitle (QString("")); - ui.projectGroupBox->layout()->addWidget (mAdjusterWidget); - - mAdjusterWidget->setVisible (false); ui.projectButtonBox->button(QDialogButtonBox::Ok)->setEnabled (false); + connect (mSelector, SIGNAL (signalAddonFileSelected (int)), this, SLOT (slotUpdateAcceptButton (int))); + connect (mSelector, SIGNAL (signalAddonFileUnselected (int)), this, SLOT (slotUpdateAcceptButton (int))); + connect (ui.projectButtonBox, SIGNAL (accepted()), this, SLOT (slotOpenFile())); } @@ -135,6 +136,11 @@ void CSVDoc::FileDialog::slotUpdateAcceptButton(const QString &name, bool) if (mDialogType == DialogType_New) success = success && !(name.isEmpty()); + else + { + ContentSelectorModel::EsmFile *file = mSelector->selectedFiles().back(); + mAdjusterWidget->setName (file->fileName(), !file->isGameFile()); + } ui.projectButtonBox->button (QDialogButtonBox::Ok)->setEnabled (success); } diff --git a/apps/opencs/view/doc/filedialog.hpp b/apps/opencs/view/doc/filedialog.hpp index 777ee31e4c..d9fd569435 100644 --- a/apps/opencs/view/doc/filedialog.hpp +++ b/apps/opencs/view/doc/filedialog.hpp @@ -5,6 +5,7 @@ #include #include +#include "adjusterwidget.hpp" #ifndef CS_QT_BOOST_FILESYSTEM_PATH_DECLARED #define CS_QT_BOOST_FILESYSTEM_PATH_DECLARED @@ -24,32 +25,23 @@ namespace ContentSelectorView namespace CSVDoc { class FileWidget; - class AdjusterWidget; class FileDialog : public QDialog { Q_OBJECT - public: - - enum DialogType - { - DialogType_New, - DialogType_Open - }; - private: ContentSelectorView::ContentSelector *mSelector; Ui::FileDialog ui; - DialogType mDialogType; + ContentAction mAction; FileWidget *mFileWidget; AdjusterWidget *mAdjusterWidget; public: explicit FileDialog(QWidget *parent = 0); - void showDialog (DialogType dialogType); + void showDialog (ContentAction action); void addFiles (const QString &path); diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index 33b31b00c5..b9e5189312 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -151,8 +151,16 @@ void ContentSelectorView::ContentSelector::slotAddonTableItemClicked(const QMode { QAbstractItemModel *const model = ui.addonView->model(); + Qt::CheckState checkState = Qt::Unchecked; + if (model->data(index, Qt::CheckStateRole).toInt() == Qt::Unchecked) - model->setData(index, Qt::Checked, Qt::CheckStateRole); + checkState = Qt::Checked; + + model->setData(index, checkState, Qt::CheckStateRole); + + if (checkState == Qt::Checked) + emit signalAddonFileSelected (index.row()); else - model->setData(index, Qt::Unchecked, Qt::CheckStateRole); + emit signalAddonFileUnselected (index.row()); + } diff --git a/components/contentselector/view/contentselector.hpp b/components/contentselector/view/contentselector.hpp index 1e24a5523e..da1c39973d 100644 --- a/components/contentselector/view/contentselector.hpp +++ b/components/contentselector/view/contentselector.hpp @@ -54,6 +54,8 @@ namespace ContentSelectorView signals: void signalCurrentGamefileIndexChanged (int); + void signalAddonFileSelected (int); + void signalAddonFileUnselected (int); private slots: From 29fce6d11f741924cf5e9d780548d4b75808d480 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 28 Oct 2013 08:45:56 +0100 Subject: [PATCH 130/434] increased version number --- CMakeLists.txt | 2 +- readme.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 625239aa06..d0de9f7712 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,7 +19,7 @@ include (OpenMWMacros) # Version set (OPENMW_VERSION_MAJOR 0) -set (OPENMW_VERSION_MINOR 26) +set (OPENMW_VERSION_MINOR 27) set (OPENMW_VERSION_RELEASE 0) set (OPENMW_VERSION "${OPENMW_VERSION_MAJOR}.${OPENMW_VERSION_MINOR}.${OPENMW_VERSION_RELEASE}") diff --git a/readme.txt b/readme.txt index 7865f8dba2..c4d9d27461 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ OpenMW: A reimplementation of The Elder Scrolls III: Morrowind OpenMW is an attempt at recreating 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. -Version: 0.26.0 +Version: 0.27.0 License: GPL (see GPL3.txt for more information) Website: http://www.openmw.org From d7584f9df7f6d39d7191aa7d8fe4448ba04b11f1 Mon Sep 17 00:00:00 2001 From: gus Date: Mon, 28 Oct 2013 18:01:40 +0100 Subject: [PATCH 131/434] getLineOfSight, no script instruction yet --- apps/openmw/mwmechanics/aisequence.cpp | 6 ++++-- apps/openmw/mwworld/worldimp.cpp | 10 ++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index a798040fe6..940002a573 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -82,7 +82,8 @@ void MWMechanics::AiSequence::execute (const MWWorld::Ptr& actor) +(actorpos.pos[2] - playerpos.pos[2])*(actorpos.pos[2] - playerpos.pos[2])); float fight = actor.getClass().getCreatureStats(actor).getAiSetting(1); float disp = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(actor); - if(fight == 100 + bool LOS = MWBase::Environment::get().getWorld()->getLOS(actor,MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); + if( ( (fight == 100 ) || (fight >= 95 && d <= 3000) || (fight >= 90 && d <= 2000) || (fight >= 80 && d <= 1000) @@ -90,7 +91,8 @@ void MWMechanics::AiSequence::execute (const MWWorld::Ptr& actor) || (fight >= 70 && disp <= 35 && d <= 1000) || (fight >= 60 && disp <= 30 && d <= 1000) || (fight >= 50 && disp == 0) - || (fight >= 40 && disp <= 10 && d <= 500) + || (fight >= 40 && disp <= 10 && d <= 500) ) + && LOS ) { mCombat = true; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 724972600a..b6d6cc2d8a 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1786,6 +1786,16 @@ namespace MWWorld bool World::getLOS(const MWWorld::Ptr& npc,const MWWorld::Ptr& targetNpc) { + Ogre::Vector3 halfExt1 = mPhysEngine->getCharacter(npc.getRefData().getHandle())->getHalfExtents(); + float* pos1 = npc.getRefData().getPosition().pos; + Ogre::Vector3 halfExt2 = mPhysEngine->getCharacter(targetNpc.getRefData().getHandle())->getHalfExtents(); + float* pos2 = targetNpc.getRefData().getPosition().pos; + + btVector3 from(pos1[0],pos1[1],pos1[2]+halfExt1.z); + btVector3 to(pos2[0],pos2[1],pos2[2]+halfExt2.z); + + std::pair result = mPhysEngine->rayTest(from, to,false); + if(result.first == "") return true; return false; } From 2d84f7d33dd2d65d5f53acf8439737d6563f6058 Mon Sep 17 00:00:00 2001 From: gus Date: Mon, 28 Oct 2013 18:10:00 +0100 Subject: [PATCH 132/434] NPC run --- apps/openmw/mwmechanics/aicombat.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 8f51c462fa..c3ac8cb551 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -35,7 +35,7 @@ namespace MWMechanics if(actor.getTypeName() == typeid(ESM::NPC).name()) { - + MWWorld::Class::get(actor).setStance(actor, MWWorld::Class::Run,true); MWMechanics::DrawState_ state = MWWorld::Class::get(actor).getNpcStats(actor).getDrawState(); if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing) MWWorld::Class::get(actor).getNpcStats(actor).setDrawState(MWMechanics::DrawState_Weapon); From 12d8b4e0f888ef50f8704db9f4ad9bd79a78ab5e Mon Sep 17 00:00:00 2001 From: gus Date: Mon, 28 Oct 2013 18:13:07 +0100 Subject: [PATCH 133/434] bugfix --- apps/openmw/mwmechanics/aicombat.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index c3ac8cb551..b01889f89b 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -31,7 +31,7 @@ namespace MWMechanics bool AiCombat::execute (const MWWorld::Ptr& actor) { - const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();//MWBase::Environment::get().getWorld()->getPtr(mTargetId, false); + const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr(mTargetId, false); if(actor.getTypeName() == typeid(ESM::NPC).name()) { From b7a92431733f729f89196713a9b19fdbd22138d1 Mon Sep 17 00:00:00 2001 From: gus Date: Mon, 28 Oct 2013 18:22:36 +0100 Subject: [PATCH 134/434] fighting should stop when the target is dead --- apps/openmw/mwmechanics/aicombat.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index b01889f89b..80ad241ef8 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -33,6 +33,8 @@ namespace MWMechanics { const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr(mTargetId, false); + if(MWWorld::Class::get(actor).getCreatureStats(actor).getHealth().getCurrent() <= 0) return true; + if(actor.getTypeName() == typeid(ESM::NPC).name()) { MWWorld::Class::get(actor).setStance(actor, MWWorld::Class::Run,true); @@ -45,8 +47,6 @@ namespace MWMechanics const ESM::Pathgrid *pathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*actor.getCell()->mCell); - int cellX = actor.getCell()->mCell->mData.mX; - int cellY = actor.getCell()->mCell->mData.mY; float xCell = 0; float yCell = 0; From 0e862092241c3f1d22bd153165b20a5df9fd3fcf Mon Sep 17 00:00:00 2001 From: gus Date: Mon, 28 Oct 2013 18:33:10 +0100 Subject: [PATCH 135/434] linux fix :p --- apps/openmw/mwmechanics/aicombat.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 80ad241ef8..66b93e8c9e 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -2,12 +2,12 @@ #include "movement.hpp" -#include "../mwworld/class.hpp" -#include "../mwworld/player.hpp" -#include "../mwworld/timestamp.hpp" -#include "../mwbase/world.hpp" -#include "../mwbase/environment.hpp" -#include "../mwbase/mechanicsmanager.hpp" +#include "..\mwworld\class.hpp" +#include "..\mwworld\player.hpp" +#include "..\mwworld\timestamp.hpp" +#include "..\mwbase\world.hpp" +#include "..\mwbase\environment.hpp" +#include "..\mwbase\mechanicsmanager.hpp" #include "OgreMath.h" From 5b20cce849d21d8ddb54bbee1c2c2cbfe1389324 Mon Sep 17 00:00:00 2001 From: gus Date: Mon, 28 Oct 2013 18:36:09 +0100 Subject: [PATCH 136/434] CMake fix --- apps/openmw/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index a44fd4b343..a35fc91974 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -68,7 +68,7 @@ add_openmw_dir (mwclass add_openmw_dir (mwmechanics mechanicsmanagerimp stat character creaturestats magiceffects movement actors objects drawstate spells activespells npcstats aipackage aisequence alchemy aiwander aitravel aifollow - aiescort aiactivate repair enchanting pathfinding security + aiescort aiactivate aicombat repair enchanting pathfinding security ) add_openmw_dir (mwbase From a6e7c6c1049d8b7a4a97fc76bdcc285d51caa7b8 Mon Sep 17 00:00:00 2001 From: gus Date: Mon, 28 Oct 2013 23:04:46 +0100 Subject: [PATCH 137/434] disable AI combat for creatures --- apps/openmw/mwmechanics/aisequence.cpp | 45 ++++++++++++++------------ 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 940002a573..f94bb4b5f8 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -75,28 +75,31 @@ void MWMechanics::AiSequence::execute (const MWWorld::Ptr& actor) } else { - ESM::Position playerpos = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getRefData().getPosition(); - ESM::Position actorpos = actor.getRefData().getPosition(); - float d = sqrt((actorpos.pos[0] - playerpos.pos[0])*(actorpos.pos[0] - playerpos.pos[0]) - +(actorpos.pos[1] - playerpos.pos[1])*(actorpos.pos[1] - playerpos.pos[1]) - +(actorpos.pos[2] - playerpos.pos[2])*(actorpos.pos[2] - playerpos.pos[2])); - float fight = actor.getClass().getCreatureStats(actor).getAiSetting(1); - float disp = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(actor); - bool LOS = MWBase::Environment::get().getWorld()->getLOS(actor,MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); - if( ( (fight == 100 ) - || (fight >= 95 && d <= 3000) - || (fight >= 90 && d <= 2000) - || (fight >= 80 && d <= 1000) - || (fight >= 80 && disp <= 40) - || (fight >= 70 && disp <= 35 && d <= 1000) - || (fight >= 60 && disp <= 30 && d <= 1000) - || (fight >= 50 && disp == 0) - || (fight >= 40 && disp <= 10 && d <= 500) ) - && LOS - ) + if(actor.getTypeName() == typeid(ESM::NPC).name()) { - mCombat = true; - mCombatPackage = new AiCombat("player"); + ESM::Position playerpos = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getRefData().getPosition(); + ESM::Position actorpos = actor.getRefData().getPosition(); + float d = sqrt((actorpos.pos[0] - playerpos.pos[0])*(actorpos.pos[0] - playerpos.pos[0]) + +(actorpos.pos[1] - playerpos.pos[1])*(actorpos.pos[1] - playerpos.pos[1]) + +(actorpos.pos[2] - playerpos.pos[2])*(actorpos.pos[2] - playerpos.pos[2])); + float fight = actor.getClass().getCreatureStats(actor).getAiSetting(1); + float disp = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(actor); + bool LOS = MWBase::Environment::get().getWorld()->getLOS(actor,MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); + if( ( (fight == 100 ) + || (fight >= 95 && d <= 3000) + || (fight >= 90 && d <= 2000) + || (fight >= 80 && d <= 1000) + || (fight >= 80 && disp <= 40) + || (fight >= 70 && disp <= 35 && d <= 1000) + || (fight >= 60 && disp <= 30 && d <= 1000) + || (fight >= 50 && disp == 0) + || (fight >= 40 && disp <= 10 && d <= 500) ) + && LOS + ) + { + mCombat = true; + mCombatPackage = new AiCombat("player"); + } } if (!mPackages.empty()) { From 45f5a66bcc8ecb0912b65b8296380dbfed5c9826 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 29 Oct 2013 09:27:23 +0100 Subject: [PATCH 138/434] use result of getAppendIndex instead of always appending at the end --- apps/opencs/model/world/collection.hpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/opencs/model/world/collection.hpp b/apps/opencs/model/world/collection.hpp index 167210de93..a4cdec4eac 100644 --- a/apps/opencs/model/world/collection.hpp +++ b/apps/opencs/model/world/collection.hpp @@ -152,7 +152,7 @@ namespace CSMWorld record2.mState = Record::State_ModifiedOnly; record2.mModified = record; - insertRecord (record2, mRecords.size()); + insertRecord (record2, getAppendIndex (id)); } else { @@ -274,7 +274,7 @@ namespace CSMWorld record2.mState = Record::State_ModifiedOnly; record2.mModified = record; - insertRecord (record2, mRecords.size(), type); + insertRecord (record2, getAppendIndex (id, type), type); } template @@ -300,7 +300,9 @@ namespace CSMWorld void Collection::appendRecord (const RecordBase& record, UniversalId::Type type) { - insertRecord (record, mRecords.size(), type); + insertRecord (record, + getAppendIndex (IdAccessorT().getId ( + dynamic_cast&> (record).get()), type), type); } template From ba88c94d585235d651a7a38fad8a53d8afd3e2c8 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 29 Oct 2013 13:18:22 +0100 Subject: [PATCH 139/434] first attempt at an info record collection --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/model/world/data.cpp | 34 ++++++++++ apps/opencs/model/world/data.hpp | 11 ++++ apps/opencs/model/world/infocollection.cpp | 72 ++++++++++++++++++++++ apps/opencs/model/world/infocollection.hpp | 20 ++++++ apps/opencs/model/world/universalid.cpp | 4 ++ apps/opencs/model/world/universalid.hpp | 4 ++ apps/opencs/view/doc/view.cpp | 18 ++++++ apps/opencs/view/doc/view.hpp | 4 ++ apps/opencs/view/world/subviews.cpp | 2 + components/esm/loadinfo.cpp | 24 ++++++++ components/esm/loadinfo.hpp | 3 + 12 files changed, 197 insertions(+), 1 deletion(-) create mode 100644 apps/opencs/model/world/infocollection.cpp create mode 100644 apps/opencs/model/world/infocollection.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 541b3b5a2c..f4846675e0 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -24,7 +24,7 @@ opencs_units (model/world opencs_units_noqt (model/world universalid record commands columnbase scriptcontext cell refidcollection - refidadapter refiddata refidadapterimp ref collectionbase refcollection columns + refidadapter refiddata refidadapterimp ref collectionbase refcollection columns infocollection ) opencs_hdrs_noqt (model/world diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 130ce334f7..2947b2b8fc 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -149,6 +149,12 @@ CSMWorld::Data::Data() : mRefs (mCells) mJournals.addColumn (new RecordStateColumn); mJournals.addColumn (new DialogueTypeColumn (true)); + mTopicInfos.addColumn (new StringIdColumn); + mTopicInfos.addColumn (new RecordStateColumn); + + mJournalInfos.addColumn (new StringIdColumn); + mJournalInfos.addColumn (new RecordStateColumn); + mCells.addColumn (new StringIdColumn); mCells.addColumn (new RecordStateColumn); mCells.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Cell)); @@ -206,6 +212,8 @@ CSMWorld::Data::Data() : mRefs (mCells) addModel (new IdTable (&mSpells), UniversalId::Type_Spells, UniversalId::Type_Spell); addModel (new IdTable (&mTopics), UniversalId::Type_Topics, UniversalId::Type_Topic); addModel (new IdTable (&mJournals), UniversalId::Type_Journals, UniversalId::Type_Journal); + addModel (new IdTable (&mTopicInfos), UniversalId::Type_TopicInfos, UniversalId::Type_TopicInfo); + addModel (new IdTable (&mJournalInfos), UniversalId::Type_JournalInfos, UniversalId::Type_JournalInfo); addModel (new IdTable (&mCells), UniversalId::Type_Cells, UniversalId::Type_Cell); addModel (new IdTable (&mReferenceables), UniversalId::Type_Referenceables, UniversalId::Type_Referenceable); @@ -350,6 +358,25 @@ CSMWorld::IdCollection& CSMWorld::Data::getJournals() return mJournals; } +const CSMWorld::InfoCollection& CSMWorld::Data::getTopicInfos() const +{ + return mTopicInfos; +} + +CSMWorld::InfoCollection& CSMWorld::Data::getTopicInfos() +{ + return mTopicInfos; +} + +const CSMWorld::InfoCollection& CSMWorld::Data::getJournalInfos() const +{ + return mJournalInfos; +} + +CSMWorld::InfoCollection& CSMWorld::Data::getJournalInfos() +{ + return mJournalInfos; +} const CSMWorld::IdCollection& CSMWorld::Data::getCells() const { @@ -514,6 +541,13 @@ void CSMWorld::Data::loadFile (const boost::filesystem::path& path, bool base) break; } + case ESM::REC_INFO: + { + /// \todo associate info record with last loaded dialogue record + mJournalInfos.load (reader, base); + break; + } + default: /// \todo throw an exception instead, once all records are implemented diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index cf31c94947..2b278fd6ff 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -29,6 +29,7 @@ #include "cell.hpp" #include "refidcollection.hpp" #include "refcollection.hpp" +#include "infocollection.hpp" class QAbstractItemModel; @@ -51,6 +52,8 @@ namespace CSMWorld IdCollection mSpells; IdCollection mTopics; IdCollection mJournals; + InfoCollection mTopicInfos; + InfoCollection mJournalInfos; IdCollection mCells; RefIdCollection mReferenceables; RefCollection mRefs; @@ -127,6 +130,14 @@ namespace CSMWorld IdCollection& getJournals(); + const InfoCollection& getTopicInfos() const; + + InfoCollection& getTopicInfos(); + + const InfoCollection& getJournalInfos() const; + + InfoCollection& getJournalInfos(); + const IdCollection& getCells() const; IdCollection& getCells(); diff --git a/apps/opencs/model/world/infocollection.cpp b/apps/opencs/model/world/infocollection.cpp new file mode 100644 index 0000000000..858f788fa5 --- /dev/null +++ b/apps/opencs/model/world/infocollection.cpp @@ -0,0 +1,72 @@ + +#include "infocollection.hpp" + +#include + +void CSMWorld::InfoCollection::load (const ESM::DialInfo& record, bool base) +{ + int index = searchId (record.mId); + + if (index==-1) + { + // new record + Record record2; + record2.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; + (base ? record2.mBase : record2.mModified) = record; + + appendRecord (record2); + } + else + { + // old record + Record record2 = getRecord (index); + + if (base) + record2.mBase = record; + else + record2.setModified (record); + + setRecord (index, record2); + } +} + +void CSMWorld::InfoCollection::load (ESM::ESMReader& reader, bool base) +{ + /// \todo put records into proper order + /// \todo adjust ID + std::string id = reader.getHNOString ("NAME"); + + if (reader.isNextSub ("DELE")) + { + int index = searchId (id); + + reader.skipRecord(); + + if (index==-1) + { + // deleting a record that does not exist + + // ignore it for now + + /// \todo report the problem to the user + } + else if (base) + { + removeRows (index, 1); + } + else + { + Record record = getRecord (index); + record.mState = RecordBase::State_Deleted; + setRecord (index, record); + } + } + else + { + ESM::DialInfo record; + record.mId = id; + record.load (reader); + + load (record, base); + } +} diff --git a/apps/opencs/model/world/infocollection.hpp b/apps/opencs/model/world/infocollection.hpp new file mode 100644 index 0000000000..8dca2b2190 --- /dev/null +++ b/apps/opencs/model/world/infocollection.hpp @@ -0,0 +1,20 @@ +#ifndef CSM_WOLRD_INFOCOLLECTION_H +#define CSM_WOLRD_INFOCOLLECTION_H + +#include + +#include "collection.hpp" + +namespace CSMWorld +{ + class InfoCollection : public Collection > + { + void load (const ESM::DialInfo& record, bool base); + + public: + + void load (ESM::ESMReader& reader, bool base); + }; +} + +#endif \ No newline at end of file diff --git a/apps/opencs/model/world/universalid.cpp b/apps/opencs/model/world/universalid.cpp index 6201a3cdaa..e633f4f696 100644 --- a/apps/opencs/model/world/universalid.cpp +++ b/apps/opencs/model/world/universalid.cpp @@ -31,6 +31,8 @@ namespace { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Spells, "Spells", 0 }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Topics, "Topics", 0 }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Journals, "Journals", 0 }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_TopicInfos, "Topic Infos", 0 }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_JournalInfos, "Journal Infos", 0 }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Cells, "Cells", 0 }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Referenceables, "Referenceables", 0 }, @@ -58,6 +60,8 @@ namespace { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Spell, "Spell", ":./spell.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Topic, "Topic", 0 }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Journal, "Journal", 0 }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_TopicInfo, "TopicInfo", 0 }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_JournalInfo, "JournalInfo", 0 }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Cell, "Cell", ":./cell.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Referenceable, "Referenceables", 0 }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Activator, "Activator", ":./activator.png" }, diff --git a/apps/opencs/model/world/universalid.hpp b/apps/opencs/model/world/universalid.hpp index ffd99e572f..0c17da03be 100644 --- a/apps/opencs/model/world/universalid.hpp +++ b/apps/opencs/model/world/universalid.hpp @@ -91,6 +91,10 @@ namespace CSMWorld Type_Topic, Type_Journals, Type_Journal, + Type_TopicInfos, + Type_TopicInfo, + Type_JournalInfos, + Type_JournalInfo, Type_Scene }; diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index 733f5b2bb1..533ca7f570 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -176,6 +176,14 @@ void CSVDoc::View::setupCharacterMenu() QAction *journals = new QAction (tr ("Journals"), this); connect (journals, SIGNAL (triggered()), this, SLOT (addJournalsSubView())); characters->addAction (journals); + + QAction *topicInfos = new QAction (tr ("Topic Infos"), this); + connect (topicInfos, SIGNAL (triggered()), this, SLOT (addTopicInfosSubView())); + characters->addAction (topicInfos); + + QAction *journalInfos = new QAction (tr ("Journal Infos"), this); + connect (journalInfos, SIGNAL (triggered()), this, SLOT (addJournalInfosSubView())); + characters->addAction (journalInfos); } void CSVDoc::View::setupAssetsMenu() @@ -436,6 +444,16 @@ void CSVDoc::View::addJournalsSubView() addSubView (CSMWorld::UniversalId::Type_Journals); } +void CSVDoc::View::addTopicInfosSubView() +{ + addSubView (CSMWorld::UniversalId::Type_TopicInfos); +} + +void CSVDoc::View::addJournalInfosSubView() +{ + addSubView (CSMWorld::UniversalId::Type_JournalInfos); +} + void CSVDoc::View::abortOperation (int type) { mDocument->abortOperation (type); diff --git a/apps/opencs/view/doc/view.hpp b/apps/opencs/view/doc/view.hpp index 7c13e374fe..13c15ec9bb 100644 --- a/apps/opencs/view/doc/view.hpp +++ b/apps/opencs/view/doc/view.hpp @@ -172,6 +172,10 @@ namespace CSVDoc void addJournalsSubView(); + void addTopicInfosSubView(); + + void addJournalInfosSubView(); + void toggleShowStatusBar (bool show); }; } diff --git a/apps/opencs/view/world/subviews.cpp b/apps/opencs/view/world/subviews.cpp index 3d98cf73ce..66e5806baf 100644 --- a/apps/opencs/view/world/subviews.cpp +++ b/apps/opencs/view/world/subviews.cpp @@ -37,6 +37,8 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) CSMWorld::UniversalId::Type_Regions, CSMWorld::UniversalId::Type_Birthsigns, CSMWorld::UniversalId::Type_Spells, + CSMWorld::UniversalId::Type_TopicInfos, + CSMWorld::UniversalId::Type_JournalInfos, CSMWorld::UniversalId::Type_None // end marker }; diff --git a/components/esm/loadinfo.cpp b/components/esm/loadinfo.cpp index 90f8fcf35b..f248e0784c 100644 --- a/components/esm/loadinfo.cpp +++ b/components/esm/loadinfo.cpp @@ -153,4 +153,28 @@ void DialInfo::save(ESMWriter &esm) } } + void DialInfo::blank() + { + mData.mUnknown1 = 0; + mData.mDisposition = 0; + mData.mRank = 0; + mData.mGender = 0; + mData.mPCrank = 0; + mData.mUnknown2 = 0; + + mSelects.clear(); + mPrev.clear(); + mNext.clear(); + mActor.clear(); + mRace.clear(); + mClass.clear(); + mNpcFaction.clear(); + mPcFaction.clear(); + mCell.clear(); + mSound.clear(); + mResponse.clear(); + mResultScript.clear(); + mFactionLess = false; + mQuestStatus = QS_None; + } } diff --git a/components/esm/loadinfo.hpp b/components/esm/loadinfo.hpp index 2361ed9eb5..badb85c386 100644 --- a/components/esm/loadinfo.hpp +++ b/components/esm/loadinfo.hpp @@ -100,6 +100,9 @@ struct DialInfo void load(ESMReader &esm); void save(ESMWriter &esm); + + void blank(); + ///< Set record to default state (does not touch the ID). }; } From 632a53ead4f175e5b2769db4649b9ead93131019 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Tue, 29 Oct 2013 13:28:43 +0100 Subject: [PATCH 140/434] Support packing the OpenCS into windows builds --- CMakeLists.txt | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 625239aa06..5a87a1a404 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -458,10 +458,20 @@ if(WIN32) "${OpenMW_SOURCE_DIR}/Daedric Font License.txt" "${OpenMW_BINARY_DIR}/settings-default.cfg" "${OpenMW_BINARY_DIR}/transparency-overrides.cfg" - "${OpenMW_BINARY_DIR}/Release/mwiniimport.exe" - "${OpenMW_BINARY_DIR}/Release/omwlauncher.exe" "${OpenMW_BINARY_DIR}/Release/openmw.exe" DESTINATION ".") + + IF(BUILD_LAUNCHER) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/omwlauncher.exe" DESTINATION ".") + ENDIF(BUILD_LAUNCHER) + IF(BUILD_MWINIIMPORTER) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/mwiniimport.exe" DESTINATION ".") + ENDIF(BUILD_MWINIIMPORTER) + IF(BUILD_OPENCS) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/opencs.exe" DESTINATION ".") + INSTALL(FILES "${OpenMW_BINARY_DIR}/opencs.cfg" DESTINATION ".") + ENDIF(BUILD_OPENCS) + INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION ".") SET(CPACK_GENERATOR "NSIS") @@ -471,7 +481,13 @@ if(WIN32) SET(CPACK_PACKAGE_VERSION_MAJOR ${OPENMW_VERSION_MAJOR}) SET(CPACK_PACKAGE_VERSION_MINOR ${OPENMW_VERSION_MINOR}) SET(CPACK_PACKAGE_VERSION_PATCH ${OPENMW_VERSION_RELEASE}) - SET(CPACK_PACKAGE_EXECUTABLES "openmw;OpenMW;omwlauncher;OpenMW Launcher") + SET(CPACK_PACKAGE_EXECUTABLES "openmw;OpenMW") + IF(BUILD_LAUNCHER) + SET(CPACK_PACKAGE_EXECUTABLES "${CPACK_PACKAGE_EXECUTABLES};omwlauncher;OpenMW Launcher") + ENDIF(BUILD_LAUNCHER) + IF(BUILD_OPENCS) + SET(CPACK_PACKAGE_EXECUTABLES "${CPACK_PACKAGE_EXECUTABLES};opencs;OpenMW Construction Set") + ENDIF(BUILD_OPENCS) SET(CPACK_NSIS_CREATE_ICONS_EXTRA "CreateShortCut '\$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\Readme.lnk' '\$INSTDIR\\\\readme.txt'") SET(CPACK_NSIS_DELETE_ICONS_EXTRA " !insertmacro MUI_STARTMENU_GETFOLDER Application $MUI_TEMP From 382ca00dcb15c6b8c684732e7ec3dd1e9a473f0e Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Tue, 29 Oct 2013 20:46:53 +0100 Subject: [PATCH 141/434] Still draft. I need to carefully read the text, and point out that this is all about record filters. --- manual/opencs/filters.tex | 46 +++++++++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/manual/opencs/filters.tex b/manual/opencs/filters.tex index 1863987dcb..72b83d270f 100644 --- a/manual/opencs/filters.tex +++ b/manual/opencs/filters.tex @@ -9,9 +9,10 @@ Don't be afraid though, filters are fairly intuitive and easy to use. \item[Filter] is generally speaking a tool able to ``Filter'' (that is: select some elements, while discarding others) according to the some criteria. In case of OpenCS: records are being filtered according to the criteria of user choice. Criteria are written down in language with simple syntax. \item[Criteria] describes condition under with any any record is being select by the filter. \item[Syntax] as you may noticed computers (in general) are rather strict, and expect only strictly formulated orders -- that is: written with correct syntax. Our syntax is simple and described in the {B}asics subsection. - \item[Expression] is a criteria, only written with OpenCS filter syntax. - \item[Token] is any part of the expression, responsible for checking for the criteria in specified column. - \item[Node] is any part of the expression, responsible for performing logical operations on tokens. That is: group two (or more) tokens together in order to create a expression that will check for criteria placed in two (again: or more) columns (logical ``or'', ``and''); create any expression that will show only records that does not met criteria of specific token(logical ``not''). + \item[Expression] is way we are actually performing filtering. Filter can be treated as ``functions'': accepts arguments, and evaluates either to the true or false for every column record. + \item[N-ary] is any expression that is useful to group two (or more) other expressions together in order to create filter that will check for criteria placed in two (again: or more) columns (logical ``or'', ``and''). + \item[unary] is any expression that expects one other expression. The example is ``not'' expression. + \item[nullary] is expression that does not accepts other expressions. It accepts arguments specified later. \end{description} \subsection{Basics} @@ -44,31 +45,54 @@ We strongly recommend to take a look at the filters table right now to see what If you want to create your own filter you have to know exactly what do you want to get in order to translate this into the tokens and nodes. Finally, you will have to write this as legal expression -- that is: using correct syntax. As a result table will show only desired rows.\\ Advance subsection covers everything that you need to know in order to create any filter you may want to. \subsection{Namespaces} -It is a ``namespace``, a term borrowed from the C++ language. In case of OpenCS namespace determinate if the filter will be stored along with your project or if it will be forgotten as soon as OpenCS quits. +It is a ``namespace``, a term borrowed from the C++ language. In case of OpenCS namespace of the filter determinate if the filter will be stored along with your project or if it will be forgotten as soon as OpenCS quits. \begin{description} \item[project::] namespace indicates that filter is stored inside the project file. \item[session::] namespace indicates that filter is not stored inside the project file, and once you will quit OpenCS (close session) the filter will be gone. Forever! Until then it can be found inside the filters table. \end{description} In addition to this two scopes, there is a third one; called one-shot. One-shot filters are not stored anywhere and as the name implies they are supposed to be created when needed only once. Good thing about the one-shot filters is that you don't need to open filters table in order to create it. Instead you just type it directly inside the filter field, starting with ``!''.\\ -Still, you may wonder how you are supposed to write expressions, what and nodes tokens are avaible, and what syntax looks like. +Still, you may wonder how you are supposed to write expressions, what expressions you should use, and what syntax looks like. -\subsubsection{Tokens} -Each token is used in similar manner. First off: you have to write it's name (for instance: ``string'') and secondly: condition that will be checked inside brackets (for instance string(something, something)). If conditions of your expression will be meet by a record (technical speaking: evaluated true) the record will show up in the table.\\ +\subsubsection{Nullary expressions} +Each expression is used in similar manner. First off: you have to write it's name (for instance: ``string'') and secondly: condition that will be checked inside brackets (for instance string(something, something)). If conditions of your expression will be meet by a record (technical speaking: evaluated true) the record will show up in the table.\\ It is clear that you need to know what are you checking, that's is: what column of the table contains information that you are interested in and what should be inside specific cell inside this column to meet your requirements. In most cases first word inside brackets sets column you want to see, while the second one sets desired value inside of the cell. To separate column from the value use comma. \paragraph{String -- string(``column'', ``value'')} -String in programmers language is often\footnote{Often, not always. There are different programming languages using slightly different terms.} just a word for anything composed of characters. In case of OpenCS this is in fact true for every value inside the column that is not composed of the pure numbers. Even columns containing only ``true`` and ``false`` values can be targeted by the string token.\footnote{There is no Boolean (''true'' or ``false'') value in the OpenCS. You should use string token for those.} String evaluates to true, when record contains in the specified column exactly the same value as specified. +String in programmers language is often\footnote{Often, not always. There are different programming languages using slightly different terms.} just a word for anything composed of characters. In case of OpenCS this is in fact true for every value inside the column that is not composed of the pure numbers. Even columns containing only ``true`` and ``false`` values can be targeted by the string token.\footnote{There is no Boolean (''true'' or ``false'') value in the OpenCS. You should use string for those.} String evaluates to true, when record contains in the specified column exactly the same value as specified. \\ -Since majority of the columns contain string values, string token is among the most often used. Examples: +Since majority of the columns contain string values, string is among the most often used expressions. Examples: \begin{itemize} \item string(``Record Type'', ``Weapon'') -- will evaluate to true for all records containing ``Weapon'' in the ``Record Type'' column cell. This group contains every weapon (including arrows and bolts) found in the game. \item string(``Portable'', ``true'') -- will evaluate to true for all records containing word true inside ``Portable'' column cell. This group contains every portable light sources (lanterns, torches etc.). \end{itemize} -This is probably enough to create around 90\% string filters you would need. However, this token is even more powerfull -- it accepts regular expressions (also called regexps). Regular expressions is a way to create string criteria that will be matched by one than just one specific value in the column. For instance, you can display both left and right gauntlets with the following expression: ``string("armor type", ".* gauntlet"))`` because ''.*'' in regexps means just: ``anything''. This filter says: please, show me ``any'' gauntlet. There are left and right gauntlets in the morrowind so this will evaluate to true for both. Simple, isn't it? +This is probably enough to create around 90\% string filters you would need. However, this expression is even more powerfull -- it accepts regular expressions (also called regexps). Regular expressions is a way to create string criteria that will be matched by one than just one specific value in the column. For instance, you can display both left and right gauntlets with the following expression: ``string("armor type", ".* gauntlet"))`` because ''.*'' in regexps means just: ``anything''. This filter says: please, show me ``any'' gauntlet. There are left and right gauntlets in the morrowind so this will evaluate to true for both. Simple, isn't it? \\ Creating regexps can be a difficult and annoying -- especially when you need complex criteria. On the other hand, We are under impression that in reality complex expressions are needed only in sporadic cases. In fact, the truth is that mostly the mentioned ``.*'' is needed and therefore the following description of regexps can be skipped by vast majority of readers. %TO-DO: write the regexps essentials. +\\ +Regular expressions is not the main topic of this manual. If you wish to learn more on this subject please, read the documentation on Qt regular expressions syntax, or TRE regexp syntax (it is almost like in Qt). \paragraph{Value -- value(``value'', (``open'', ``close''))} -While string token covers vast group of columns containing string values, there are in fact columns with just numerical values like ``weight``. To filter those we need a value token. This one works in similar manner to the string filter: first token name and criteria inside brackets. Clearly, conditions should hold column to test in. However in this case wanted value is specified as a range. \ No newline at end of file +While string expression covers vast group of columns containing string values, there are in fact columns with just numerical values like ``weight``. To filter those we need a value expression. This one works in similar manner to the string filter: first token name and criteria inside brackets. Clearly, conditions should hold column to test in. However in this case wanted value is specified as a range.\\ +As you would imagine the range can be specified as including a border value, or excluding. We are using two types of brackets for this: +\begin{itemize} + \item To include value use [] brackets. For value equal 5, expression value(something, [5, 10]) will evaluate to true. + \item To exclude value use () brackets. For value equal 5, expression value(something, (5, 10)) will evaluate to false. + \item Mixing brackets is completely legal. For value equal 10, expression value(something, [5, 10) will evaluate to true. The same expression will evaluate to false for value equal 10. +\end{itemize} + +\subsection{Logical expressions} +This subsection takes care of two remaining groups of expressions: binary and unary. The only unary expression present in the OpenCS is logical not, while the remaining binary expressions are: or, and. This clearly makes theme from user point of view belonging to the same group of logical expressions. + +\paragraph{not -- not expression()} +Sometimes you may be in need of reversing the output of the expression. This is where not comes in handy. Adding not before expression will revert it: if expression was returning true, it will return false; if it was returning false, it will return true. Brackets are not needed: not will revert only the first expression following it.\\ +To show this on know example, let's consider the ''string("armor type", ".* gauntlet"))`` filter. As We mentioned earlier this will return true for every gauntlet found in game. In order to show everything, but gauntlets we simply do ''not string("armor type", ".* gauntlet"))``. This is probably not the most useful filter on earth, but this is not a surprise: real value of not expression shines when combined with or, and filter. + +\paragraph{or -- or(expression1(), expression2())} +Or is a expression that will return true if one of the arguments evaluates to true. You can use two or more arguments, separated by the comma.\\ +Or expression is useful when showing two different group of records is needed. For instance the standard actor filter is using the following ''or(string(``record type'', npc), string(``record type'', creature))`` and will show both npcs and creatures. + +\paragraph{and -- and(expression1(), expression2())} +And is a expression that will return true if all arguments evaluates to true. As in the case of ''or`` you can use two or more arguments, separated by the comma.\\ +As We mentioned earlier in the ''not`` filter, combining not with and can be very useful. For instance to show all armor types, excluding gauntlets you can write the following: ''and (not string("armor type", ".* gauntlet"), string(''Record Type``, ''Armor``))''. \ No newline at end of file From 056833e21e50d8492589846f5f878f48779a0a15 Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Wed, 30 Oct 2013 12:57:01 +0100 Subject: [PATCH 142/434] Still filters. A little less draft-ish quality. --- manual/opencs/filters.tex | 43 ++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/manual/opencs/filters.tex b/manual/opencs/filters.tex index 72b83d270f..418ead3fc5 100644 --- a/manual/opencs/filters.tex +++ b/manual/opencs/filters.tex @@ -8,10 +8,10 @@ Don't be afraid though, filters are fairly intuitive and easy to use. \begin{description} \item[Filter] is generally speaking a tool able to ``Filter'' (that is: select some elements, while discarding others) according to the some criteria. In case of OpenCS: records are being filtered according to the criteria of user choice. Criteria are written down in language with simple syntax. \item[Criteria] describes condition under with any any record is being select by the filter. - \item[Syntax] as you may noticed computers (in general) are rather strict, and expect only strictly formulated orders -- that is: written with correct syntax. Our syntax is simple and described in the {B}asics subsection. - \item[Expression] is way we are actually performing filtering. Filter can be treated as ``functions'': accepts arguments, and evaluates either to the true or false for every column record. - \item[N-ary] is any expression that is useful to group two (or more) other expressions together in order to create filter that will check for criteria placed in two (again: or more) columns (logical ``or'', ``and''). - \item[unary] is any expression that expects one other expression. The example is ``not'' expression. + \item[Syntax] as you may noticed computers (in general) are rather strict, and expect only strictly formulated orders -- that is: written with correct syntax. + \item[Expression] is way we are actually performing filtering. Filter can be treated as ``functions'': accepts arguments, and evaluates either to the true or false for every column record at the time. + \item[N-ary] is any expression that expects two or more expressions as arguments. It is useful for grouping two (or more) other expressions together in order to create filter that will check for criteria placed in two (again: or more) columns (logical ``or'', ``and''). + \item[unary] is any expression that expects one other expression. The example is ``not'' expression. In fact ``not'' is the only useful unary expression in OpenCS record filters. \item[nullary] is expression that does not accepts other expressions. It accepts arguments specified later. \end{description} @@ -25,53 +25,54 @@ Above each table there is a field that is used to enter filter: either predefine \item[ID] contains the name of the filter. \item[Modified] just like in all other tables you have seen so far modified indicates if a filter was added, modified or removed. \item[Filter] column containing expression of the filter. - \item[Description] contains the short description of the filter function. + \item[Description] contains the short description of the filter function. Do not expect any surprises there. \end{description} So let's learn how to actually use those to speed up your work. \subsection{Using predefined filters} -Using those filters is quite easy and involves typing inside the filter field above the table. For instance, try to open referencables table and type in the filters field the following: ``project::weapons''. As soon as you complete the text table will magicly alters and will show only the weapons. As you could noticed project::weapons is nothing else than a name of one of the predefined filters. That's it: in order to use the filter inside the table you simply type it's name inside the filter field.\\ -To make life easier filter names follow simple convention. +Using those filters is quite easy and involves typing inside the filter field above the table. For instance, try to open referencables table and type in the filters field the following: ``project::weapons''. As soon as you complete the text, table will magicly alter and will show only the weapons. As you could noticed project::weapons is nothing else than a ID of one of the predefined filters. That's it: in order to use the filter inside the table you simply type it's name inside the filter field.\\ +To make life easier filter IDs follow simple convention. \begin{itemize} - \item Filter name filtering a specific record type contains usually the name of specific group. For instance project::weapons filter contains the word weapons (did you noticed?). Plural form is always used. - \item When filtering specific subgroup the name starts just like in the case of general filter. For instance project::weaponssilver will filter only silver weapons (new mechanic introduced by the Bloodmoon, silver weapons deal double damage against werewolfs) and project::weaponsmagical will filter only magical weapons (able to hurt ghosts and other supernatural creatures). - \item There are few exceptions from the above. For instance there is a project::added, project::removed, project::modyfied, project::base. You would probably except something more like ``project::statusadded'' but in this case typing this few extra characters would only help to break your keyboard faster. + \item Filter ID filtering a specific record type contains usually the name of a specific group. For instance project::weapons filter contains the word weapons (did you noticed?). Plural form is always used. + \item When filtering specific subgroup the ID starts just like in the case of general filter. For instance project::weaponssilver will filter only silver weapons (new mechanic introduced by the Bloodmoon, silver weapons deal double damage against werewolfs) and project::weaponsmagical will filter only magical weapons (able to hurt ghosts and other supernatural creatures). + \item There are few exceptions from the above rule. For instance there is a project::added, project::removed, project::modyfied, project::base. You would probably except something more like ``project::statusadded'' but in this case typing this few extra characters would only help to break your keyboard faster. \end{itemize} We strongly recommend to take a look at the filters table right now to see what you can filter with that. And try using it! It is very simple. \subsection{Advanced} -If you want to create your own filter you have to know exactly what do you want to get in order to translate this into the tokens and nodes. Finally, you will have to write this as legal expression -- that is: using correct syntax. As a result table will show only desired rows.\\ +Back to the manual? Great.\\ +If you want to create your own filter you have to know exactly what do you want to get in order to translate this into the expressions. Finally, you will have to write this with correct syntax. As a result table will show only desired rows.\\ Advance subsection covers everything that you need to know in order to create any filter you may want to. \subsection{Namespaces} -It is a ``namespace``, a term borrowed from the C++ language. In case of OpenCS namespace of the filter determinate if the filter will be stored along with your project or if it will be forgotten as soon as OpenCS quits. +Did you noticed that every default filter has ``project::`` prefix? It is a ``namespace``, a term borrowed from the C++ language. In case of OpenCS namespace of the filter determinate if the filter will be stored along with your project file or if it will be forgotten as soon as OpenCS quits. \begin{description} \item[project::] namespace indicates that filter is stored inside the project file. \item[session::] namespace indicates that filter is not stored inside the project file, and once you will quit OpenCS (close session) the filter will be gone. Forever! Until then it can be found inside the filters table. \end{description} In addition to this two scopes, there is a third one; called one-shot. One-shot filters are not stored anywhere and as the name implies they are supposed to be created when needed only once. Good thing about the one-shot filters is that you don't need to open filters table in order to create it. Instead you just type it directly inside the filter field, starting with ``!''.\\ -Still, you may wonder how you are supposed to write expressions, what expressions you should use, and what syntax looks like. +Still, you may wonder how you are supposed to write expressions, what expressions you should use, and what syntax looks like. Let's start with nullary expressions that will allow you to create a basic filter. \subsubsection{Nullary expressions} -Each expression is used in similar manner. First off: you have to write it's name (for instance: ``string'') and secondly: condition that will be checked inside brackets (for instance string(something, something)). If conditions of your expression will be meet by a record (technical speaking: evaluated true) the record will show up in the table.\\ -It is clear that you need to know what are you checking, that's is: what column of the table contains information that you are interested in and what should be inside specific cell inside this column to meet your requirements. In most cases first word inside brackets sets column you want to see, while the second one sets desired value inside of the cell. To separate column from the value use comma. +All nullary expressions are used in similar manner. First off: you have to write it's name (for instance: ``string'') and secondly: condition that will be checked inside brackets (for instance string(something, something)). If conditions of your expression will be meet by a record (technical speaking: expression will evaluate to true) the record will show up in the table.\\ +It is clear that you need to know what are you checking, that's is: what column of the table contains information that you are interested in and what should be inside specific cell inside this column to meet your requirements. In most cases first word inside brackets sets column you want to see, while the second one sets desired value inside of the cell. To separate column argument from the value argument use comma. \paragraph{String -- string(``column'', ``value'')} -String in programmers language is often\footnote{Often, not always. There are different programming languages using slightly different terms.} just a word for anything composed of characters. In case of OpenCS this is in fact true for every value inside the column that is not composed of the pure numbers. Even columns containing only ``true`` and ``false`` values can be targeted by the string token.\footnote{There is no Boolean (''true'' or ``false'') value in the OpenCS. You should use string for those.} String evaluates to true, when record contains in the specified column exactly the same value as specified. +String in programmers language is often\footnote{Often, not always. There are different programming languages using slightly different terms.} just a word for anything composed of characters. In case of OpenCS this is in fact true for every value inside the column that is not composed of the pure numbers. Even columns containing only ``true`` and ``false`` values can be targeted by the string expression.\footnote{There is no Boolean (''true'' or ``false'') value in the OpenCS. You should use string for those.} String evaluates to true, when record contains in the specified column exactly the same value as specified. \\ Since majority of the columns contain string values, string is among the most often used expressions. Examples: \begin{itemize} \item string(``Record Type'', ``Weapon'') -- will evaluate to true for all records containing ``Weapon'' in the ``Record Type'' column cell. This group contains every weapon (including arrows and bolts) found in the game. \item string(``Portable'', ``true'') -- will evaluate to true for all records containing word true inside ``Portable'' column cell. This group contains every portable light sources (lanterns, torches etc.). \end{itemize} -This is probably enough to create around 90\% string filters you would need. However, this expression is even more powerfull -- it accepts regular expressions (also called regexps). Regular expressions is a way to create string criteria that will be matched by one than just one specific value in the column. For instance, you can display both left and right gauntlets with the following expression: ``string("armor type", ".* gauntlet"))`` because ''.*'' in regexps means just: ``anything''. This filter says: please, show me ``any'' gauntlet. There are left and right gauntlets in the morrowind so this will evaluate to true for both. Simple, isn't it? +This is probably enough to create around 90\% string filters you will eventually need. However, this expression is even more powerful -- it accepts regular expressions (also called regexps). Regular expressions is a way to create string criteria that will be matched by one than just one specific value in the column. For instance, you can display both left and right gauntlets with the following expression: ``string("armor type", ".* gauntlet"))`` because ''.*'' in regexps means just: ``anything''. This filter says: please, show me ``any'' gauntlet. There are left and right gauntlets in the morrowind so this will evaluate to true for both. Simple, isn't it? \\ -Creating regexps can be a difficult and annoying -- especially when you need complex criteria. On the other hand, We are under impression that in reality complex expressions are needed only in sporadic cases. In fact, the truth is that mostly the mentioned ``.*'' is needed and therefore the following description of regexps can be skipped by vast majority of readers. +Creating regexps can be a difficult and annoying -- especially when you need complex criteria. On the other hand, We are under impression that in reality complex expressions are needed only in sporadic cases. In fact, the truth is: that most of the time only already mentioned ``.*'' is needed and therefore the following description of regexps can be skipped by vast majority of readers. %TO-DO: write the regexps essentials. \\ -Regular expressions is not the main topic of this manual. If you wish to learn more on this subject please, read the documentation on Qt regular expressions syntax, or TRE regexp syntax (it is almost like in Qt). +Regular expressions are not the main topic of this manual. If you wish to learn more on this subject please, read the documentation on Qt regular expressions syntax, or TRE regexp syntax (it is almost like in Qt). \paragraph{Value -- value(``value'', (``open'', ``close''))} While string expression covers vast group of columns containing string values, there are in fact columns with just numerical values like ``weight``. To filter those we need a value expression. This one works in similar manner to the string filter: first token name and criteria inside brackets. Clearly, conditions should hold column to test in. However in this case wanted value is specified as a range.\\ @@ -94,5 +95,5 @@ Or is a expression that will return true if one of the arguments evaluates to tr Or expression is useful when showing two different group of records is needed. For instance the standard actor filter is using the following ''or(string(``record type'', npc), string(``record type'', creature))`` and will show both npcs and creatures. \paragraph{and -- and(expression1(), expression2())} -And is a expression that will return true if all arguments evaluates to true. As in the case of ''or`` you can use two or more arguments, separated by the comma.\\ -As We mentioned earlier in the ''not`` filter, combining not with and can be very useful. For instance to show all armor types, excluding gauntlets you can write the following: ''and (not string("armor type", ".* gauntlet"), string(''Record Type``, ''Armor``))''. \ No newline at end of file +And is a expression that will return true if all arguments evaluates to true. As in the case of ''or`` you can use two or more arguments, separated by a comma.\\ +As We mentioned earlier in the ''not`` filter, combining ''not`` with ''and`` can be very useful. For instance to show all armor types, excluding gauntlets you can write the following: ''and (not string("armor type", ".* gauntlet"), string(''Record Type``, ''Armor``))''. \ No newline at end of file From 4f35fd8184bbe45da391f39e9ec49064fd26deb5 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 30 Oct 2013 13:05:28 +0100 Subject: [PATCH 143/434] Removed a workaround from Renderer, some cleanup --- apps/openmw/engine.cpp | 3 +- apps/openmw/mwbase/environment.cpp | 1 + apps/openmw/mwbase/environment.hpp | 5 ++++ apps/openmw/mwgui/mainmenu.cpp | 2 +- apps/openmw/mwinput/inputmanagerimp.cpp | 10 ------- apps/openmw/mwinput/inputmanagerimp.hpp | 3 +- libs/openengine/ogre/renderer.cpp | 39 +------------------------ libs/openengine/ogre/renderer.hpp | 25 ---------------- 8 files changed, 11 insertions(+), 77 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index e1fd3a0af3..7e344c4dba 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -503,7 +503,8 @@ void OMW::Engine::go() MWBase::Environment::get().getWindowManager()->executeInConsole (mStartupScript); // Start the main rendering loop - mOgre->start(); + while (!mEnvironment.getRequestExit()) + Ogre::Root::getSingleton().renderOneFrame(); // Save user settings settings.saveUser(settingspath); diff --git a/apps/openmw/mwbase/environment.cpp b/apps/openmw/mwbase/environment.cpp index 5a13a50ec9..6b309025cc 100644 --- a/apps/openmw/mwbase/environment.cpp +++ b/apps/openmw/mwbase/environment.cpp @@ -13,6 +13,7 @@ #include "windowmanager.hpp" MWBase::Environment *MWBase::Environment::sThis = 0; +bool MWBase::Environment::sExit = false; MWBase::Environment::Environment() : mWorld (0), mSoundManager (0), mScriptManager (0), mWindowManager (0), diff --git a/apps/openmw/mwbase/environment.hpp b/apps/openmw/mwbase/environment.hpp index a80e7ef870..4663029077 100644 --- a/apps/openmw/mwbase/environment.hpp +++ b/apps/openmw/mwbase/environment.hpp @@ -32,6 +32,8 @@ namespace MWBase InputManager *mInputManager; float mFrameDuration; + static bool sExit; + Environment (const Environment&); ///< not implemented @@ -44,6 +46,9 @@ namespace MWBase ~Environment(); + static void setRequestExit () { sExit = true; } + static bool getRequestExit () { return sExit; } + void setWorld (World *world); void setSoundManager (SoundManager *soundManager); diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp index 1db6e9ecd2..2c42dc210f 100644 --- a/apps/openmw/mwgui/mainmenu.cpp +++ b/apps/openmw/mwgui/mainmenu.cpp @@ -77,7 +77,7 @@ namespace MWGui else if (sender == mButtons["options"]) MWBase::Environment::get().getWindowManager ()->pushGuiMode (GM_Settings); else if (sender == mButtons["exitgame"]) - Ogre::Root::getSingleton ().queueEndRendering (); + MWBase::Environment::get().setRequestExit(); else if (sender == mButtons["newgame"]) { MWBase::Environment::get().getWorld()->startNewGame(); diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 4746260edf..c4a360cd4b 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -182,9 +182,6 @@ namespace MWInput case A_GameMenu: toggleMainMenu (); break; - case A_Quit: - exitNow(); - break; case A_Screenshot: screenshot(); break; @@ -814,13 +811,6 @@ namespace MWInput mAlwaysRunActive = !mAlwaysRunActive; } - // Exit program now button (which is disabled in GUI mode) - void InputManager::exitNow() - { - if(!MWBase::Environment::get().getWindowManager()->isGuiMode()) - Ogre::Root::getSingleton().queueEndRendering (); - } - void InputManager::resetIdleTime() { if (mTimeIdle < 0) diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index 5f9a752d72..b38c80e0e2 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -177,7 +177,6 @@ namespace MWInput void activate(); void toggleWalking(); void toggleAutoMove(); - void exitNow(); void rest(); void quickKey (int index); @@ -194,7 +193,7 @@ namespace MWInput A_GameMenu, - A_Quit, // Exit the program + A_Unused, A_Screenshot, // Take a screenshot diff --git a/libs/openengine/ogre/renderer.cpp b/libs/openengine/ogre/renderer.cpp index 9127812408..d078e3c61e 100644 --- a/libs/openengine/ogre/renderer.cpp +++ b/libs/openengine/ogre/renderer.cpp @@ -28,21 +28,6 @@ using namespace Ogre; using namespace OEngine::Render; -#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE - -CustomRoot::CustomRoot(const Ogre::String& pluginFileName, - const Ogre::String& configFileName, - const Ogre::String& logFileName) -: Ogre::Root(pluginFileName, configFileName, logFileName) -{} - -bool CustomRoot::isQueuedEnd() const -{ - return mQueuedEnd; -} - -#endif - void OgreRenderer::cleanup() { delete mFader; @@ -60,23 +45,6 @@ void OgreRenderer::cleanup() unloadPlugins(); } -void OgreRenderer::start() -{ -#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE - // we need this custom main loop because otherwise Ogre's Carbon message pump will - // steal input events even from our Cocoa window - // There's no way to disable Ogre's message pump other that comment pump code in Ogre's source - do { - if (!mRoot->renderOneFrame()) { - break; - } - - } while (!mRoot->isQueuedEnd()); -#else - mRoot->startRendering(); -#endif -} - void OgreRenderer::loadPlugins() { #ifdef ENABLE_PLUGIN_GL @@ -158,20 +126,15 @@ void OgreRenderer::configure(const std::string &logPath, // Set up logging first new LogManager; Log *log = LogManager::getSingleton().createLog(logPath + std::string("Ogre.log")); - logging = _logging; - if(logging) + if(_logging) // Full log detail log->setLogDetail(LL_BOREME); else // Disable logging log->setDebugOutputEnabled(false); -#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE - mRoot = new CustomRoot("", "", ""); -#else mRoot = new Root("", "", ""); -#endif #if defined(ENABLE_PLUGIN_GL) || defined(ENABLE_PLUGIN_Direct3D9) || defined(ENABLE_PLUGIN_CgProgramManager) || defined(ENABLE_PLUGIN_OctreeSceneManager) || defined(ENABLE_PLUGIN_ParticleFX) loadPlugins(); diff --git a/libs/openengine/ogre/renderer.hpp b/libs/openengine/ogre/renderer.hpp index 89edc567dd..c6a838805e 100644 --- a/libs/openengine/ogre/renderer.hpp +++ b/libs/openengine/ogre/renderer.hpp @@ -27,18 +27,13 @@ #include "OgreTexture.h" #include -#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE -#include -#endif struct SDL_Window; struct SDL_Surface; namespace Ogre { -#if OGRE_PLATFORM != OGRE_PLATFORM_APPLE class Root; -#endif class RenderWindow; class SceneManager; class Camera; @@ -61,17 +56,6 @@ namespace OEngine std::string icon; }; -#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE - class CustomRoot : public Ogre::Root { - public: - bool isQueuedEnd() const; - - CustomRoot(const Ogre::String& pluginFileName = "plugins.cfg", - const Ogre::String& configFileName = "ogre.cfg", - const Ogre::String& logFileName = "Ogre.log"); - }; -#endif - class Fader; class WindowSizeListener @@ -82,11 +66,7 @@ namespace OEngine class OgreRenderer { -#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE - CustomRoot *mRoot; -#else Ogre::Root *mRoot; -#endif Ogre::RenderWindow *mWindow; SDL_Window *mSDLWindow; Ogre::SceneManager *mScene; @@ -110,7 +90,6 @@ namespace OEngine Fader* mFader; std::vector mEmitterFactories; std::vector mAffectorFactories; - bool logging; WindowSizeListener* mWindowListener; @@ -139,7 +118,6 @@ namespace OEngine , mD3D9Plugin(NULL) #endif , mFader(NULL) - , logging(false) { } @@ -167,9 +145,6 @@ namespace OEngine /// Kill the renderer. void cleanup(); - /// Start the main rendering loop - void start(); - void loadPlugins(); void unloadPlugins(); From 636d399c7f89b854db2621499f54b17042d94b77 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 30 Oct 2013 14:04:33 +0100 Subject: [PATCH 144/434] Refactored Ogre initialisation into a component --- CMakeLists.txt | 1 - apps/launcher/graphicspage.cpp | 43 +---- apps/launcher/graphicspage.hpp | 20 +-- apps/launcher/main.cpp | 2 - apps/openmw/engine.cpp | 3 +- components/CMakeLists.txt | 8 +- .../ogre => components/nifogre}/particles.cpp | 0 .../ogre => components/nifogre}/particles.hpp | 0 components/ogreinit/ogreinit.cpp | 165 ++++++++++++++++++ components/ogreinit/ogreinit.hpp | 75 ++++++++ components/{files => ogreinit}/ogreplugin.cpp | 0 components/{files => ogreinit}/ogreplugin.hpp | 0 libs/openengine/ogre/renderer.cpp | 149 +--------------- libs/openengine/ogre/renderer.hpp | 62 +------ 14 files changed, 267 insertions(+), 261 deletions(-) rename {libs/openengine/ogre => components/nifogre}/particles.cpp (100%) rename {libs/openengine/ogre => components/nifogre}/particles.hpp (100%) create mode 100644 components/ogreinit/ogreinit.cpp create mode 100644 components/ogreinit/ogreinit.hpp rename components/{files => ogreinit}/ogreplugin.cpp (100%) rename components/{files => ogreinit}/ogreplugin.hpp (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 625239aa06..6fd64aa3fd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,7 +79,6 @@ set(OENGINE_OGRE ${LIBDIR}/openengine/ogre/renderer.cpp ${LIBDIR}/openengine/ogre/fader.cpp ${LIBDIR}/openengine/ogre/lights.cpp - ${LIBDIR}/openengine/ogre/particles.cpp ${LIBDIR}/openengine/ogre/selectionbuffer.cpp ${LIBDIR}/openengine/ogre/imagerotate.cpp ) diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index 97a24d54bd..b7f59c781c 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -10,12 +10,9 @@ #endif #include -#include - #include #include -#include #include @@ -54,13 +51,9 @@ GraphicsPage::GraphicsPage(Files::ConfigurationManager &cfg, GraphicsSettings &g bool GraphicsPage::setupOgre() { - // Create a log manager so we can surpress debug text to stdout/stderr - Ogre::LogManager* logMgr = OGRE_NEW Ogre::LogManager; - logMgr->createLog((mCfgMgr.getLogPath().string() + "/launcherOgre.log"), true, false, false); - try { - mOgre = new Ogre::Root("", "", "./launcherOgre.log"); + mOgre = mOgreInit.init(mCfgMgr.getLogPath().string() + "/launcherOgre.log"); } catch(Ogre::Exception &ex) { @@ -78,40 +71,6 @@ bool GraphicsPage::setupOgre() return false; } - - std::string pluginDir; - const char* pluginEnv = getenv("OPENMW_OGRE_PLUGIN_DIR"); - if (pluginEnv) - pluginDir = pluginEnv; - else - { -#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 - pluginDir = ".\\"; -#endif -#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE - pluginDir = OGRE_PLUGIN_DIR; -#endif -#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX - pluginDir = OGRE_PLUGIN_DIR_REL; -#endif - } - - QDir dir(QString::fromStdString(pluginDir)); - pluginDir = dir.absolutePath().toStdString(); - - Files::loadOgrePlugin(pluginDir, "RenderSystem_GL", *mOgre); - Files::loadOgrePlugin(pluginDir, "RenderSystem_GL3Plus", *mOgre); - Files::loadOgrePlugin(pluginDir, "RenderSystem_Direct3D9", *mOgre); - -#ifdef ENABLE_PLUGIN_GL - mGLPlugin = new Ogre::GLPlugin(); - mOgre->installPlugin(mGLPlugin); -#endif -#ifdef ENABLE_PLUGIN_Direct3D9 - mD3D9Plugin = new Ogre::D3D9Plugin(); - mOgre->installPlugin(mD3D9Plugin); -#endif - // Get the available renderers and put them in the combobox const Ogre::RenderSystemList &renderers = mOgre->getAvailableRenderers(); diff --git a/apps/launcher/graphicspage.hpp b/apps/launcher/graphicspage.hpp index d233ea12e2..29a95c36a5 100644 --- a/apps/launcher/graphicspage.hpp +++ b/apps/launcher/graphicspage.hpp @@ -5,16 +5,9 @@ #include #include -//#include -//#include -// Static plugin headers -#ifdef ENABLE_PLUGIN_GL -# include "OgreGLPlugin.h" -#endif -#ifdef ENABLE_PLUGIN_Direct3D9 -# include "OgreD3D9Plugin.h" -#endif +#include + #include "ui_graphicspage.h" @@ -41,16 +34,13 @@ private slots: void slotStandardToggled(bool checked); private: + OgreInit::OgreInit mOgreInit; + Ogre::Root *mOgre; Ogre::RenderSystem *mSelectedRenderSystem; Ogre::RenderSystem *mOpenGLRenderSystem; Ogre::RenderSystem *mDirect3DRenderSystem; - #ifdef ENABLE_PLUGIN_GL - Ogre::GLPlugin* mGLPlugin; - #endif - #ifdef ENABLE_PLUGIN_Direct3D9 - Ogre::D3D9Plugin* mD3D9Plugin; - #endif + Files::ConfigurationManager &mCfgMgr; GraphicsSettings &mGraphicsSettings; diff --git a/apps/launcher/main.cpp b/apps/launcher/main.cpp index f67f5edcff..cb1a51d8c1 100644 --- a/apps/launcher/main.cpp +++ b/apps/launcher/main.cpp @@ -10,8 +10,6 @@ #include #include "maindialog.hpp" -// SDL workaround -#include "graphicspage.hpp" int main(int argc, char *argv[]) { diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 7e344c4dba..020283f2d5 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -357,8 +357,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) mOgre->configure( mCfgMgr.getLogPath().string(), renderSystem, - Settings::Manager::getString("opengl rtt mode", "Video"), - false); + Settings::Manager::getString("opengl rtt mode", "Video")); // This has to be added BEFORE MyGUI is initialized, as it needs // to find core.xml here. diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 04423dc6f0..ef1556038b 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -19,7 +19,7 @@ add_component_dir (nif ) add_component_dir (nifogre - ogrenifloader skeleton material mesh + ogrenifloader skeleton material mesh particles ) add_component_dir (nifbullet @@ -48,7 +48,7 @@ add_component_dir (misc add_component_dir (files linuxpath windowspath macospath fixedpath multidircollection collections fileops configurationmanager - filelibrary ogreplugin constrainedfiledatastream lowlevelfile + filelibrary constrainedfiledatastream lowlevelfile ) add_component_dir (compiler @@ -74,6 +74,10 @@ add_component_dir (loadinglistener loadinglistener ) +add_component_dir (ogreinit + ogreinit ogreplugin + ) + find_package(Qt4 COMPONENTS QtCore QtGui) if(QT_QTGUI_LIBRARY AND QT_QTCORE_LIBRARY) diff --git a/libs/openengine/ogre/particles.cpp b/components/nifogre/particles.cpp similarity index 100% rename from libs/openengine/ogre/particles.cpp rename to components/nifogre/particles.cpp diff --git a/libs/openengine/ogre/particles.hpp b/components/nifogre/particles.hpp similarity index 100% rename from libs/openengine/ogre/particles.hpp rename to components/nifogre/particles.hpp diff --git a/components/ogreinit/ogreinit.cpp b/components/ogreinit/ogreinit.cpp new file mode 100644 index 0000000000..92a6ed0123 --- /dev/null +++ b/components/ogreinit/ogreinit.cpp @@ -0,0 +1,165 @@ +#include "ogreinit.hpp" + +#include + +#include +#include +#include + +#include + +#include "ogreplugin.hpp" + +namespace OgreInit +{ + + OgreInit::OgreInit() + : mRoot(NULL) + #ifdef ENABLE_PLUGIN_CgProgramManager + , mCgPlugin(NULL) + #endif + #ifdef ENABLE_PLUGIN_OctreeSceneManager + , mOctreePlugin(NULL) + #endif + #ifdef ENABLE_PLUGIN_ParticleFX + , mParticleFXPlugin(NULL) + #endif + #ifdef ENABLE_PLUGIN_GL + , mGLPlugin(NULL) + #endif + #ifdef ENABLE_PLUGIN_Direct3D9 + , mD3D9Plugin(NULL) + #endif + {} + + Ogre::Root* OgreInit::init(const std::string &logPath) + { + // Set up logging first + new Ogre::LogManager; + Ogre::Log *log = Ogre::LogManager::getSingleton().createLog(logPath + std::string("Ogre.log")); + + // Disable logging to cout/cerr + log->setDebugOutputEnabled(false); + + mRoot = new Ogre::Root("", "", ""); + + #if defined(ENABLE_PLUGIN_GL) || defined(ENABLE_PLUGIN_Direct3D9) || defined(ENABLE_PLUGIN_CgProgramManager) || defined(ENABLE_PLUGIN_OctreeSceneManager) || defined(ENABLE_PLUGIN_ParticleFX) + loadStaticPlugins(); + #else + loadPlugins(); + #endif + + loadParticleFactories(); + + return mRoot; + } + + OgreInit::~OgreInit() + { + delete mRoot; + + std::vector::iterator ei; + for(ei = mEmitterFactories.begin();ei != mEmitterFactories.end();++ei) + OGRE_DELETE (*ei); + mEmitterFactories.clear(); + + std::vector::iterator ai; + for(ai = mAffectorFactories.begin();ai != mAffectorFactories.end();++ai) + OGRE_DELETE (*ai); + mAffectorFactories.clear(); + + #ifdef ENABLE_PLUGIN_GL + delete mGLPlugin; + mGLPlugin = NULL; + #endif + #ifdef ENABLE_PLUGIN_Direct3D9 + delete mD3D9Plugin; + mD3D9Plugin = NULL; + #endif + #ifdef ENABLE_PLUGIN_CgProgramManager + delete mCgPlugin; + mCgPlugin = NULL; + #endif + #ifdef ENABLE_PLUGIN_OctreeSceneManager + delete mOctreePlugin; + mOctreePlugin = NULL; + #endif + #ifdef ENABLE_PLUGIN_ParticleFX + delete mParticleFXPlugin; + mParticleFXPlugin = NULL; + #endif + } + + void OgreInit::loadStaticPlugins() + { + #ifdef ENABLE_PLUGIN_GL + mGLPlugin = new Ogre::GLPlugin(); + mRoot->installPlugin(mGLPlugin); + #endif + #ifdef ENABLE_PLUGIN_Direct3D9 + mD3D9Plugin = new Ogre::D3D9Plugin(); + mRoot->installPlugin(mD3D9Plugin); + #endif + #ifdef ENABLE_PLUGIN_CgProgramManager + mCgPlugin = new Ogre::CgPlugin(); + mRoot->installPlugin(mCgPlugin); + #endif + #ifdef ENABLE_PLUGIN_OctreeSceneManager + mOctreePlugin = new Ogre::OctreePlugin(); + mRoot->installPlugin(mOctreePlugin); + #endif + #ifdef ENABLE_PLUGIN_ParticleFX + mParticleFXPlugin = new Ogre::ParticleFXPlugin(); + mRoot->installPlugin(mParticleFXPlugin); + #endif + } + + void OgreInit::loadPlugins() + { + std::string pluginDir; + const char* pluginEnv = getenv("OPENMW_OGRE_PLUGIN_DIR"); + if (pluginEnv) + pluginDir = pluginEnv; + else + { + #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 + pluginDir = ".\\"; + #endif + #if OGRE_PLATFORM == OGRE_PLATFORM_APPLE + pluginDir = OGRE_PLUGIN_DIR; + #endif + #if OGRE_PLATFORM == OGRE_PLATFORM_LINUX + pluginDir = OGRE_PLUGIN_DIR_REL; + #endif + } + + boost::filesystem::path absPluginPath = boost::filesystem::absolute(boost::filesystem::path(pluginDir)); + + pluginDir = absPluginPath.string(); + + Files::loadOgrePlugin(pluginDir, "RenderSystem_GL", *mRoot); + Files::loadOgrePlugin(pluginDir, "RenderSystem_GLES2", *mRoot); + Files::loadOgrePlugin(pluginDir, "RenderSystem_GL3Plus", *mRoot); + Files::loadOgrePlugin(pluginDir, "RenderSystem_Direct3D9", *mRoot); + Files::loadOgrePlugin(pluginDir, "Plugin_CgProgramManager", *mRoot); + Files::loadOgrePlugin(pluginDir, "Plugin_ParticleFX", *mRoot); + } + + void OgreInit::loadParticleFactories() + { + Ogre::ParticleEmitterFactory *emitter; + emitter = OGRE_NEW NifEmitterFactory(); + Ogre::ParticleSystemManager::getSingleton().addEmitterFactory(emitter); + mEmitterFactories.push_back(emitter); + + Ogre::ParticleAffectorFactory *affector; + affector = OGRE_NEW GrowFadeAffectorFactory(); + Ogre::ParticleSystemManager::getSingleton().addAffectorFactory(affector); + mAffectorFactories.push_back(affector); + + affector = OGRE_NEW GravityAffectorFactory(); + Ogre::ParticleSystemManager::getSingleton().addAffectorFactory(affector); + mAffectorFactories.push_back(affector); + } + +} diff --git a/components/ogreinit/ogreinit.hpp b/components/ogreinit/ogreinit.hpp new file mode 100644 index 0000000000..b6fe4631a8 --- /dev/null +++ b/components/ogreinit/ogreinit.hpp @@ -0,0 +1,75 @@ +#ifndef OPENMW_COMPONENTS_OGREINIT_H +#define OPENMW_COMPONENTS_OGREINIT_H + +#include +#include + +// Static plugin headers +#ifdef ENABLE_PLUGIN_CgProgramManager +# include "OgreCgPlugin.h" +#endif +#ifdef ENABLE_PLUGIN_OctreeSceneManager +# include "OgreOctreePlugin.h" +#endif +#ifdef ENABLE_PLUGIN_ParticleFX +# include "OgreParticleFXPlugin.h" +#endif +#ifdef ENABLE_PLUGIN_GL +# include "OgreGLPlugin.h" +#endif +#ifdef ENABLE_PLUGIN_Direct3D9 +# include "OgreD3D9Plugin.h" +#endif + +namespace Ogre +{ + class ParticleEmitterFactory; + class ParticleAffectorFactory; + class Root; +} + +namespace OgreInit +{ + /** + * @brief Starts Ogre::Root and loads required plugins and NIF particle factories + */ + class OgreInit + { + public: + OgreInit(); + + Ogre::Root* init(const std::string &logPath // Path to directory where to store log files + ); + + ~OgreInit(); + + private: + std::vector mEmitterFactories; + std::vector mAffectorFactories; + Ogre::Root* mRoot; + + void loadStaticPlugins(); + void loadPlugins(); + void loadParticleFactories(); + + + #ifdef ENABLE_PLUGIN_CgProgramManager + Ogre::CgPlugin* mCgPlugin; + #endif + #ifdef ENABLE_PLUGIN_OctreeSceneManager + Ogre::OctreePlugin* mOctreePlugin; + #endif + #ifdef ENABLE_PLUGIN_ParticleFX + Ogre::ParticleFXPlugin* mParticleFXPlugin; + #endif + #ifdef ENABLE_PLUGIN_GL + Ogre::GLPlugin* mGLPlugin; + #endif + #ifdef ENABLE_PLUGIN_Direct3D9 + Ogre::D3D9Plugin* mD3D9Plugin; + #endif + + }; +} + +#endif diff --git a/components/files/ogreplugin.cpp b/components/ogreinit/ogreplugin.cpp similarity index 100% rename from components/files/ogreplugin.cpp rename to components/ogreinit/ogreplugin.cpp diff --git a/components/files/ogreplugin.hpp b/components/ogreinit/ogreplugin.hpp similarity index 100% rename from components/files/ogreplugin.hpp rename to components/ogreinit/ogreplugin.hpp diff --git a/libs/openengine/ogre/renderer.cpp b/libs/openengine/ogre/renderer.cpp index d078e3c61e..2a438e1fef 100644 --- a/libs/openengine/ogre/renderer.cpp +++ b/libs/openengine/ogre/renderer.cpp @@ -1,27 +1,17 @@ #include "renderer.hpp" #include "fader.hpp" -#include "particles.hpp" #include -#include "OgreRoot.h" -#include "OgreRenderWindow.h" -#include "OgreLogManager.h" -#include "OgreLog.h" -#include "OgreTextureManager.h" -#include "OgreTexture.h" -#include "OgreHardwarePixelBuffer.h" -#include -#include "OgreParticleAffectorFactory.h" - -#include - -#include +#include +#include +#include +#include +#include #include #include -#include #include using namespace Ogre; @@ -33,74 +23,11 @@ void OgreRenderer::cleanup() delete mFader; mFader = NULL; - delete mRoot; - mRoot = NULL; - // If we don't do this, the desktop resolution is not restored on exit SDL_SetWindowFullscreen(mSDLWindow, 0); SDL_DestroyWindow(mSDLWindow); mSDLWindow = NULL; - - unloadPlugins(); -} - -void OgreRenderer::loadPlugins() -{ - #ifdef ENABLE_PLUGIN_GL - mGLPlugin = new Ogre::GLPlugin(); - mRoot->installPlugin(mGLPlugin); - #endif - #ifdef ENABLE_PLUGIN_Direct3D9 - mD3D9Plugin = new Ogre::D3D9Plugin(); - mRoot->installPlugin(mD3D9Plugin); - #endif - #ifdef ENABLE_PLUGIN_CgProgramManager - mCgPlugin = new Ogre::CgPlugin(); - mRoot->installPlugin(mCgPlugin); - #endif - #ifdef ENABLE_PLUGIN_OctreeSceneManager - mOctreePlugin = new Ogre::OctreePlugin(); - mRoot->installPlugin(mOctreePlugin); - #endif - #ifdef ENABLE_PLUGIN_ParticleFX - mParticleFXPlugin = new Ogre::ParticleFXPlugin(); - mRoot->installPlugin(mParticleFXPlugin); - #endif -} - -void OgreRenderer::unloadPlugins() -{ - std::vector::iterator ei; - for(ei = mEmitterFactories.begin();ei != mEmitterFactories.end();++ei) - OGRE_DELETE (*ei); - mEmitterFactories.clear(); - - std::vector::iterator ai; - for(ai = mAffectorFactories.begin();ai != mAffectorFactories.end();++ai) - OGRE_DELETE (*ai); - mAffectorFactories.clear(); - - #ifdef ENABLE_PLUGIN_GL - delete mGLPlugin; - mGLPlugin = NULL; - #endif - #ifdef ENABLE_PLUGIN_Direct3D9 - delete mD3D9Plugin; - mD3D9Plugin = NULL; - #endif - #ifdef ENABLE_PLUGIN_CgProgramManager - delete mCgPlugin; - mCgPlugin = NULL; - #endif - #ifdef ENABLE_PLUGIN_OctreeSceneManager - delete mOctreePlugin; - mOctreePlugin = NULL; - #endif - #ifdef ENABLE_PLUGIN_ParticleFX - delete mParticleFXPlugin; - mParticleFXPlugin = NULL; - #endif } void OgreRenderer::update(float dt) @@ -120,70 +47,10 @@ float OgreRenderer::getFPS() void OgreRenderer::configure(const std::string &logPath, const std::string& renderSystem, - const std::string& rttMode, - bool _logging) + const std::string& rttMode + ) { - // Set up logging first - new LogManager; - Log *log = LogManager::getSingleton().createLog(logPath + std::string("Ogre.log")); - - if(_logging) - // Full log detail - log->setLogDetail(LL_BOREME); - else - // Disable logging - log->setDebugOutputEnabled(false); - - mRoot = new Root("", "", ""); - - #if defined(ENABLE_PLUGIN_GL) || defined(ENABLE_PLUGIN_Direct3D9) || defined(ENABLE_PLUGIN_CgProgramManager) || defined(ENABLE_PLUGIN_OctreeSceneManager) || defined(ENABLE_PLUGIN_ParticleFX) - loadPlugins(); - #endif - - std::string pluginDir; - const char* pluginEnv = getenv("OPENMW_OGRE_PLUGIN_DIR"); - if (pluginEnv) - pluginDir = pluginEnv; - else - { -#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 - pluginDir = ".\\"; -#endif -#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE - pluginDir = OGRE_PLUGIN_DIR; -#endif -#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX - pluginDir = OGRE_PLUGIN_DIR_REL; -#endif - } - - boost::filesystem::path absPluginPath = boost::filesystem::absolute(boost::filesystem::path(pluginDir)); - - pluginDir = absPluginPath.string(); - - Files::loadOgrePlugin(pluginDir, "RenderSystem_GL", *mRoot); - Files::loadOgrePlugin(pluginDir, "RenderSystem_GLES2", *mRoot); - Files::loadOgrePlugin(pluginDir, "RenderSystem_GL3Plus", *mRoot); - Files::loadOgrePlugin(pluginDir, "RenderSystem_Direct3D9", *mRoot); - Files::loadOgrePlugin(pluginDir, "Plugin_CgProgramManager", *mRoot); - Files::loadOgrePlugin(pluginDir, "Plugin_ParticleFX", *mRoot); - - - Ogre::ParticleEmitterFactory *emitter; - emitter = OGRE_NEW NifEmitterFactory(); - Ogre::ParticleSystemManager::getSingleton().addEmitterFactory(emitter); - mEmitterFactories.push_back(emitter); - - - Ogre::ParticleAffectorFactory *affector; - affector = OGRE_NEW GrowFadeAffectorFactory(); - Ogre::ParticleSystemManager::getSingleton().addAffectorFactory(affector); - mAffectorFactories.push_back(affector); - - affector = OGRE_NEW GravityAffectorFactory(); - Ogre::ParticleSystemManager::getSingleton().addAffectorFactory(affector); - mAffectorFactories.push_back(affector); - + mRoot = mOgreInit.init(logPath); RenderSystem* rs = mRoot->getRenderSystemByName(renderSystem); if (rs == 0) diff --git a/libs/openengine/ogre/renderer.hpp b/libs/openengine/ogre/renderer.hpp index c6a838805e..e4af0bf77c 100644 --- a/libs/openengine/ogre/renderer.hpp +++ b/libs/openengine/ogre/renderer.hpp @@ -7,25 +7,9 @@ #include -// Static plugin headers -#ifdef ENABLE_PLUGIN_CgProgramManager -# include "OgreCgPlugin.h" -#endif -#ifdef ENABLE_PLUGIN_OctreeSceneManager -# include "OgreOctreePlugin.h" -#endif -#ifdef ENABLE_PLUGIN_ParticleFX -# include "OgreParticleFXPlugin.h" -#endif -#ifdef ENABLE_PLUGIN_GL -# include "OgreGLPlugin.h" -#endif -#ifdef ENABLE_PLUGIN_Direct3D9 -# include "OgreD3D9Plugin.h" -#endif +#include -#include "OgreTexture.h" -#include +#include struct SDL_Window; @@ -72,24 +56,10 @@ namespace OEngine Ogre::SceneManager *mScene; Ogre::Camera *mCamera; Ogre::Viewport *mView; - #ifdef ENABLE_PLUGIN_CgProgramManager - Ogre::CgPlugin* mCgPlugin; - #endif - #ifdef ENABLE_PLUGIN_OctreeSceneManager - Ogre::OctreePlugin* mOctreePlugin; - #endif - #ifdef ENABLE_PLUGIN_ParticleFX - Ogre::ParticleFXPlugin* mParticleFXPlugin; - #endif - #ifdef ENABLE_PLUGIN_GL - Ogre::GLPlugin* mGLPlugin; - #endif - #ifdef ENABLE_PLUGIN_Direct3D9 - Ogre::D3D9Plugin* mD3D9Plugin; - #endif + + OgreInit::OgreInit mOgreInit; + Fader* mFader; - std::vector mEmitterFactories; - std::vector mAffectorFactories; WindowSizeListener* mWindowListener; @@ -102,21 +72,6 @@ namespace OEngine , mCamera(NULL) , mView(NULL) , mWindowListener(NULL) - #ifdef ENABLE_PLUGIN_CgProgramManager - , mCgPlugin(NULL) - #endif - #ifdef ENABLE_PLUGIN_OctreeSceneManager - , mOctreePlugin(NULL) - #endif - #ifdef ENABLE_PLUGIN_ParticleFX - , mParticleFXPlugin(NULL) - #endif - #ifdef ENABLE_PLUGIN_GL - , mGLPlugin(NULL) - #endif - #ifdef ENABLE_PLUGIN_Direct3D9 - , mD3D9Plugin(NULL) - #endif , mFader(NULL) { } @@ -128,8 +83,7 @@ namespace OEngine void configure( const std::string &logPath, // Path to directory where to store log files const std::string &renderSystem, - const std::string &rttMode, - bool _logging); // Enable or disable logging + const std::string &rttMode); // Enable or disable logging /// Create a window with the given title void createWindow(const std::string &title, const WindowSettings& settings); @@ -145,10 +99,6 @@ namespace OEngine /// Kill the renderer. void cleanup(); - void loadPlugins(); - - void unloadPlugins(); - void update(float dt); /// Write a screenshot to file From 650a112e2e5168ae3ab26d5ebf8a9c98d5ed46b5 Mon Sep 17 00:00:00 2001 From: gus Date: Wed, 30 Oct 2013 20:42:50 +0100 Subject: [PATCH 145/434] better timer --- apps/openmw/mwmechanics/actors.cpp | 2 +- apps/openmw/mwmechanics/aiactivate.cpp | 2 +- apps/openmw/mwmechanics/aiactivate.hpp | 2 +- apps/openmw/mwmechanics/aicombat.cpp | 20 ++++++++++++-------- apps/openmw/mwmechanics/aicombat.hpp | 4 ++-- apps/openmw/mwmechanics/aiescort.cpp | 2 +- apps/openmw/mwmechanics/aiescort.hpp | 2 +- apps/openmw/mwmechanics/aifollow.cpp | 2 +- apps/openmw/mwmechanics/aifollow.hpp | 2 +- apps/openmw/mwmechanics/aipackage.hpp | 2 +- apps/openmw/mwmechanics/aisequence.cpp | 6 +++--- apps/openmw/mwmechanics/aisequence.hpp | 2 +- apps/openmw/mwmechanics/aitravel.cpp | 2 +- apps/openmw/mwmechanics/aitravel.hpp | 2 +- apps/openmw/mwmechanics/aiwander.cpp | 2 +- apps/openmw/mwmechanics/aiwander.hpp | 2 +- 16 files changed, 30 insertions(+), 26 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 566d4bc505..52c1685a03 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -35,7 +35,7 @@ namespace MWMechanics if(!MWBase::Environment::get().getWindowManager()->isGuiMode()) { CreatureStats& creatureStats = MWWorld::Class::get (ptr).getCreatureStats (ptr); - creatureStats.getAiSequence().execute (ptr); + creatureStats.getAiSequence().execute (ptr,duration); } } diff --git a/apps/openmw/mwmechanics/aiactivate.cpp b/apps/openmw/mwmechanics/aiactivate.cpp index b94c8c2599..ee0dcf96e5 100644 --- a/apps/openmw/mwmechanics/aiactivate.cpp +++ b/apps/openmw/mwmechanics/aiactivate.cpp @@ -9,7 +9,7 @@ MWMechanics::AiActivate *MWMechanics::AiActivate::clone() const { return new AiActivate(*this); } -bool MWMechanics::AiActivate::execute (const MWWorld::Ptr& actor) +bool MWMechanics::AiActivate::execute (const MWWorld::Ptr& actor,float duration) { std::cout << "AiActivate completed.\n"; return true; diff --git a/apps/openmw/mwmechanics/aiactivate.hpp b/apps/openmw/mwmechanics/aiactivate.hpp index 7f3d4016dc..f922e238c2 100644 --- a/apps/openmw/mwmechanics/aiactivate.hpp +++ b/apps/openmw/mwmechanics/aiactivate.hpp @@ -12,7 +12,7 @@ namespace MWMechanics public: AiActivate(const std::string &objectId); virtual AiActivate *clone() const; - virtual bool execute (const MWWorld::Ptr& actor); + virtual bool execute (const MWWorld::Ptr& actor,float duration); ///< \return Package completed? virtual int getTypeId() const; diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 66b93e8c9e..bab183f119 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -25,11 +25,11 @@ namespace MWMechanics { AiCombat::AiCombat(const std::string &targetId) - :mTargetId(targetId),mStartingSecond(0) + :mTargetId(targetId),mTimer(0) { } - bool AiCombat::execute (const MWWorld::Ptr& actor) + bool AiCombat::execute (const MWWorld::Ptr& actor,float duration) { const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr(mTargetId, false); @@ -97,18 +97,22 @@ namespace MWMechanics MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false); mPathFinder.clearPath(); - - MWWorld::TimeStamp time = MWBase::Environment::get().getWorld()->getTimeStamp(); - if(mStartingSecond == 0) + + if(mTimer == 0) { MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(false); - mStartingSecond = ((time.getHour() - int(time.getHour())) * 100); + //mTimer = mTimer + duration; } - else if( ((time.getHour() - int(time.getHour())) * 100) - mStartingSecond > 1) + if( mTimer > 1) { MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(true); - mStartingSecond = 0; + mTimer = 0; } + else + { + mTimer = mTimer + duration; + } + MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0; //MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(!MWWorld::Class::get(actor).getCreatureStats(actor).getAttackingOrSpell()); } diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index f61582adc8..8e2c3cb94f 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -24,7 +24,7 @@ namespace MWMechanics virtual AiCombat *clone() const; - virtual bool execute (const MWWorld::Ptr& actor); + virtual bool execute (const MWWorld::Ptr& actor,float duration); ///< \return Package completed? virtual int getTypeId() const; @@ -34,7 +34,7 @@ namespace MWMechanics PathFinder mPathFinder; PathFinder mPathFinder2; - unsigned int mStartingSecond; + float mTimer; }; } diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index 556e0b1267..3615c8546e 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -71,7 +71,7 @@ namespace MWMechanics return new AiEscort(*this); } - bool AiEscort::execute (const MWWorld::Ptr& actor) + bool AiEscort::execute (const MWWorld::Ptr& actor,float duration) { // If AiEscort has ran for as long or longer then the duration specified // and the duration is not infinite, the package is complete. diff --git a/apps/openmw/mwmechanics/aiescort.hpp b/apps/openmw/mwmechanics/aiescort.hpp index 3ae604035a..f3f6d2bd9b 100644 --- a/apps/openmw/mwmechanics/aiescort.hpp +++ b/apps/openmw/mwmechanics/aiescort.hpp @@ -18,7 +18,7 @@ namespace MWMechanics virtual AiEscort *clone() const; - virtual bool execute (const MWWorld::Ptr& actor); + virtual bool execute (const MWWorld::Ptr& actor,float duration); ///< \return Package completed? virtual int getTypeId() const; diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index dab9e02839..73bf9259af 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -15,7 +15,7 @@ MWMechanics::AiFollow *MWMechanics::AiFollow::clone() const return new AiFollow(*this); } - bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor) + bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration) { std::cout << "AiFollow completed.\n"; return true; diff --git a/apps/openmw/mwmechanics/aifollow.hpp b/apps/openmw/mwmechanics/aifollow.hpp index 0b37b0a2d9..39df024e4b 100644 --- a/apps/openmw/mwmechanics/aifollow.hpp +++ b/apps/openmw/mwmechanics/aifollow.hpp @@ -13,7 +13,7 @@ namespace MWMechanics 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); virtual AiFollow *clone() const; - virtual bool execute (const MWWorld::Ptr& actor); + virtual bool execute (const MWWorld::Ptr& actor,float duration); ///< \return Package completed? virtual int getTypeId() const; diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index d286fbba89..794708f227 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -17,7 +17,7 @@ namespace MWMechanics virtual AiPackage *clone() const = 0; - virtual bool execute (const MWWorld::Ptr& actor) = 0; + virtual bool execute (const MWWorld::Ptr& actor,float duration) = 0; ///< \return Package completed? virtual int getTypeId() const = 0; diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index f94bb4b5f8..bdde23e0d3 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -65,13 +65,13 @@ bool MWMechanics::AiSequence::isPackageDone() const return mDone; } -void MWMechanics::AiSequence::execute (const MWWorld::Ptr& actor) +void MWMechanics::AiSequence::execute (const MWWorld::Ptr& actor,float duration) { if(actor != MWBase::Environment::get().getWorld()->getPlayer().getPlayer()) { if(mCombat) { - mCombatPackage->execute(actor); + mCombatPackage->execute(actor,duration); } else { @@ -103,7 +103,7 @@ void MWMechanics::AiSequence::execute (const MWWorld::Ptr& actor) } if (!mPackages.empty()) { - if (mPackages.front()->execute (actor)) + if (mPackages.front()->execute (actor,duration)) { mPackages.erase (mPackages.begin()); mDone = true; diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index 37084a1e5b..ea2a048f16 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -41,7 +41,7 @@ namespace MWMechanics bool isPackageDone() const; ///< Has a package been completed during the last update? - void execute (const MWWorld::Ptr& actor); + void execute (const MWWorld::Ptr& actor,float duration); ///< Execute package. void clear(); diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index d47a49c702..08d7586388 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -31,7 +31,7 @@ namespace MWMechanics return new AiTravel(*this); } - bool AiTravel::execute (const MWWorld::Ptr& actor) + bool AiTravel::execute (const MWWorld::Ptr& actor,float duration) { MWBase::World *world = MWBase::Environment::get().getWorld(); ESM::Position pos = actor.getRefData().getPosition(); diff --git a/apps/openmw/mwmechanics/aitravel.hpp b/apps/openmw/mwmechanics/aitravel.hpp index 6eb9af8cec..b479dfd431 100644 --- a/apps/openmw/mwmechanics/aitravel.hpp +++ b/apps/openmw/mwmechanics/aitravel.hpp @@ -13,7 +13,7 @@ namespace MWMechanics AiTravel(float x, float y, float z); virtual AiTravel *clone() const; - virtual bool execute (const MWWorld::Ptr& actor); + virtual bool execute (const MWWorld::Ptr& actor,float duration); ///< \return Package completed? virtual int getTypeId() const; diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 96a41883b9..ecc41489a5 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -63,7 +63,7 @@ namespace MWMechanics return new AiWander(*this); } - bool AiWander::execute (const MWWorld::Ptr& actor) + bool AiWander::execute (const MWWorld::Ptr& actor,float duration) { MWBase::World *world = MWBase::Environment::get().getWorld(); if(mDuration) diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index c82ccc2155..48bc62c625 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -16,7 +16,7 @@ namespace MWMechanics AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat); virtual AiPackage *clone() const; - virtual bool execute (const MWWorld::Ptr& actor); + virtual bool execute (const MWWorld::Ptr& actor,float duration); ///< \return Package completed? virtual int getTypeId() const; ///< 0: Wander From 2a927f65d396e8c9d249e55a42a90f8376e18195 Mon Sep 17 00:00:00 2001 From: gus Date: Wed, 30 Oct 2013 20:52:23 +0100 Subject: [PATCH 146/434] perfomances optimisations (pathfinding is computed less often, max 4 times per sec) --- apps/openmw/mwmechanics/aicombat.cpp | 11 ++++++++--- apps/openmw/mwmechanics/aicombat.hpp | 1 + 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index bab183f119..0a64abec32 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -25,7 +25,7 @@ namespace MWMechanics { AiCombat::AiCombat(const std::string &targetId) - :mTargetId(targetId),mTimer(0) + :mTargetId(targetId),mTimer(0),mTimer2(0) { } @@ -66,15 +66,20 @@ namespace MWMechanics start.mY = pos.pos[1]; start.mZ = pos.pos[2]; + mTimer2 = mTimer2 + duration; + if(!mPathFinder.isPathConstructed()) mPathFinder.buildPath(start, dest, pathgrid, xCell, yCell, true); else { mPathFinder2.buildPath(start, dest, pathgrid, xCell, yCell, true); ESM::Pathgrid::Point lastPt = mPathFinder.getPath().back(); - if(mPathFinder2.getPathSize() < mPathFinder.getPathSize() || - (dest.mX - lastPt.mX)*(dest.mX - lastPt.mX)+(dest.mY - lastPt.mY)*(dest.mY - lastPt.mY)+(dest.mZ - lastPt.mZ)*(dest.mZ - lastPt.mZ) > 200*200) + if((mTimer2 > 0.25)&&(mPathFinder2.getPathSize() < mPathFinder.getPathSize() || + (dest.mX - lastPt.mX)*(dest.mX - lastPt.mX)+(dest.mY - lastPt.mY)*(dest.mY - lastPt.mY)+(dest.mZ - lastPt.mZ)*(dest.mZ - lastPt.mZ) > 200*200)) + { + mTimer2 = 0; mPathFinder = mPathFinder2; + } } mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2]); diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 8e2c3cb94f..7321b2cde6 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -35,6 +35,7 @@ namespace MWMechanics PathFinder mPathFinder; PathFinder mPathFinder2; float mTimer; + float mTimer2; }; } From cb8d111d36866b7ff98db3d80162f62908e95d45 Mon Sep 17 00:00:00 2001 From: TomKoenderink Date: Wed, 30 Oct 2013 21:51:37 +0100 Subject: [PATCH 147/434] Started tables.tex; import library for images --- manual/opencs/img/water.png | Bin 0 -> 914 bytes manual/opencs/main.tex | 3 +- manual/opencs/tables.tex | 78 ++++++++++++++++++++++++++++++++++-- 3 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 manual/opencs/img/water.png diff --git a/manual/opencs/img/water.png b/manual/opencs/img/water.png new file mode 100644 index 0000000000000000000000000000000000000000..885c2d9a732fbcc138dd5b82675136753af27848 GIT binary patch literal 914 zcmeAS@N?(olHy`uVBq!ia0y~yVC)34^ElXmezgNlPf3)4h~MgeXYCYD8t3&8?SXZ`dT4V2tws5Er)=Lxf_aUO7K^kkB`rpDzX zq#&Rm)DWNu#0o+T9V`j698?$tI1gx-dwuwsF3=HMlYdBsG2xISSk$D^qk&lhXf%@x zQ^UuaDZD;{<(~^9CbKXA%@j$TbwGu&!Gi&85Q7q<&k1n`S0)Dy7bXTEn#kaw(jZ_n zU4RqFI{-8ZuI|XQoh@MAGR}z%GAn@Y`Jo7N%K=3$B}NWKC6Ku@)LfVf3MSooB=p42 z;@tGt7ehrk1UTD|E&FdCWWpl&X!FXheXC?D7i{}eo6hNa)YIvRYf~qG=QQaZ-``su zP7L|_R&AjGr<%Qj+|Ew-fAc2tFMf4ixg|+&o~MO^Ou_*z`NmR)-Ph{$fMzlSt&1~I z6f$GIA^hb*Fq@fk*3A7jFa9M=65wE%+~P3d@6}ab6J{BBGH|T$VBm0--B*~q(0Rw- z$GR&l?r=0}D87#0TYBHXvxE6Yd{ZJ&lhHn{t!_TzN{Rv&F>3@5dYr!PYi2*?R`($# zp+^jFZXG|g@m#B5hf$Rr%e8LPNoqpx=bLx`lwWVJujeEbfByb89>wcD9vm#LW_wMO z-=;jC+0xrrdidP$XiW*F#L3BT3!eX7C%~O=e)Gvavkhe&yS6EP2v5i@S|huLePYD! zg=Us~ENi$YZa7%^V(<346`z<&R+c?JV72IZ)Xcw@M^7F*=9n-0@QtRW=Zy;y{xdXO z4!JFRd2H*l6*-byEfuRY)&xs@@HuoWx$Wg;tD`r+FMn;mF4tzkuAB3Ge;hMqc&wb* t1xzxqIA%zQP_fyM?b@h2jZF!oGqTO-KPjj#h$KyF6*2UngG_9Pgno| literal 0 HcmV?d00001 diff --git a/manual/opencs/main.tex b/manual/opencs/main.tex index da3457e133..64f9baf5c9 100644 --- a/manual/opencs/main.tex +++ b/manual/opencs/main.tex @@ -1,12 +1,13 @@ \documentclass[american]{article} \usepackage[T1]{fontenc} \usepackage{babel} +\usepackage{graphicx} \author{OpenMW Team} \begin{document} \title{OpenCS User Manual} - \maketitle \tableofcontents{} +\input{tables} \input{filters} \end{document} diff --git a/manual/opencs/tables.tex b/manual/opencs/tables.tex index eab308d420..1a9b362d1b 100644 --- a/manual/opencs/tables.tex +++ b/manual/opencs/tables.tex @@ -1,10 +1,82 @@ \section{Tables} -If you launched OpenCS already and played it with for a while you surely noticed that it is a very table oriented application. Your impression is surely correct: major part of Open{CS} is built around table pattern. But this is not excel clone! Table was just the most logical way of dealing with all different record types in a general way. + +\subsection{Introduction} +If you have launched OpenCS already and played around with it for a bit, you have probably gotten the impression that it contains lots of tables. You'd be spot on: OpenCS is built around using tables. This doesn't mean it works just like Excel or Calc, though. Due to the vast amounts of information involved with Morrowind, tables just made the most sense. You have to be able to spot information quickly and be able to change them on the fly. Let's browse through the various screens and see what all these tables show. + + + \subsection{Used Terms} +\subsubsection{Glossary} + \begin{description} + \item[Record:] An entry in OpenCS representing an item, location, sound, NPC or anything else. + + \item[Reference, Referenceable:] When an item is placed in the world, it doesn't create a new record each time. For example, the game world might contain a lot of exquisite belts on different NPCs and in many crates, but they all refer to one specific record: the Exquisite Belt record. In this case, all those belts in crates and on NPCs are references. The central Exquisite Belt record is called a referenceable. This allows modders to make changes to all items of the same type. For example, if you want all exquisite belts to have 4000 enchantment points rather than 400, you will only need to change the referenceable Exquisite Belt rather than all exquisite belts references individually. \end{description} -\subsection{Basics} +\subsubsection{Recurring Terms} -\subsection{Advanced} +Some columns are recurring throughout OpenCS. They show up in (nearly) every table in OpenCS. + +\begin{description} + \item[ID]: Each item, location, sound, etc. gets the same unique identifier in both OpenCS and Morrowind. This is usually a very self-explanatory name. For example, the ID for the (unique) black pants of Caius Cosades is "Caius_pants". This allows you to manipulate the game in many ways. For example, you could add these pants to your inventory by simply opening the console and write: player->addItem Caius_pants. Either way, in both Morrowind and OpenCS, the ID is the primary way to identify all these different parts of the game. + + \item[Modified]: This column shows what has happened (if something has happened) to this record. There are four possible states in which it can exist. + \begin{description} + \item[Base] means that this record is part of the base game and is in its original state. Usually, if you create a mod, the base game is Morrowind with optionally the Bloodmoon and Tribunal expansions. + \item[Added] means that this record was not in the base game and has been added by a modder. + \item[Modified] means that the record is part of the base game, but has been changed in some way. + \item[Deleted] means that this record used to be part of the base game, but has been removed as an entry. This does not mean, however, that the occurrences in the game itself have been removed! For example, if you remove the CharGen_Bed entry from morrowind.esm, it doesn't mean the bedroll in the basement of the Census and Excise Office in Seyda Neen is gone. You're going to have to delete that reference yourself or make sure that that object is replaced by something that still exists otherwise you'll get crashes in the worst case scenario. + \end{description} +\end{description} + + + +\subsection{World Screens} + +The contents of the game world can be changed by choosing one of the options in the appropriate menu at the top of the screen. + +\subsubsection{Regions} + +This describes the general areas of Vvardenfell. Each of these areas has different rules about things such as encounters and weather. + +\begin{description} + \item[Name:] This is how the game will show your location in-game. + \item[Map Colour:] This is a six-digit hexidecimal representation of the colour used to identify the region on the map available in World > Region Map. If you don't have an application with a colour picker, you can use your favourite search engine to find a colour picker online. + \item[Sleep Encounter:] These are the rules for what kind of enemies you might encounter when you sleep outside in the wild. +\end{description} + +\subsubsection{Cells} + +Expansive worlds such as Vvardenfell, with all its items, NPCs, etc. have a lot going on simultaneously. But if you are in Balmora, why would the computer need to keep track the exact locations of NPCs walking through the corridors in a Vivec canton? All that work would be quite useless and bring your system to its knees! So the world has been divided up into squares we call "cells". Once your character enters a cell, the game will load everything that is going on in that cell so you can interact with it. + +In the original Morrowind this could be seen when you were travelling and you would see a small loading bar at the bottom of the screen; you had just entered a new cell and the game would have to load all the items and NPCs. The Cells screen in OpenCS provides you with a list of cells in the game, both the interior cells (houses, dungeons, mines, etc.) and the exterior cells (the outside world). + +\begin{description} + \item[Sleep Forbidden:] Can the player sleep on the floor? In most cities it is forbidden to sleep outside. Sleeping in the wild carries its own risks of attack, though, and this entry lets you decide if a player should be allowed to sleep on the floor in this cell or not. + + \item[Interior Water:] Should water be rendered in this interior cell? The game world consists of an endless ocean at height 0. Then the landscape is added. If part of the landscape goes below height 0, the player will see water. (See illustration.) + + Setting the cell's Interior Water to true tells the game that this cell is both an interior cell (inside a building, for example, rather than in the open air) but that there still needs to be water at height 0. This is useful for dungeons or mines that have water in them. + + Setting the cell's Interior Water to false tells the game that the water at height 0 should not be used. Remember that cells that are in the outside world are exterior cells and should thus \textit{always} be set to false! + + \item[Interior Sky:] Should this interior cell have a sky? This is a rather unique case. The \textit{Tribunal} expansion took place in a city on the mainland. Normally this would require the city to be composed of exterior cells so it has a sky, weather and the like. But if the player is in an exterior cell and looks at his in-game map, he sees Vvardenfell with an overview of all exterior cells. The player would have to see the city's very own map, as if he was walking around in an interior cell. + + So the developers decided to create a workaround and take a bit of both: The whole city would technically work exactly like an interior cell, but it would need a sky as if it was an exterior cell. That's what this is. This is why the vast majority of the cells you will find in this screen will have this option set to false: It's only meant for these "fake exteriors". + + \item[Region:] To which Region does this cell belong? This has an impact on the way the game handles weather and encounters in this area. It is also possible for a cell not to belong to any region. + +\end{description} + +\subsubsection{Referenceables} + +This is a library of all the items, triggers, containers, NPCs, etc. in the game. There are several kinds of Record Types. Depending on which type a record is, it will need specific information to function. For example, an NPC needs a value attached to its aggression level. A chest, of course, does not. All Record Types contain at least a model. How else would the player see them? Usually they also have a Name, which is what you see when you hover your reticle over the object. + +Let's go through all Record Types and discuss what you can tell OpenCS about them. + +\begin{description} + \item[Activator:] This is an item that, when activated, starts a script or even just shows a tooltip. + \end{description} +\end{description} \ No newline at end of file From 02ded18fc55d313fbd39e6e683a7e8726c702020 Mon Sep 17 00:00:00 2001 From: gus Date: Thu, 31 Oct 2013 09:43:12 +0100 Subject: [PATCH 148/434] Linux backslash fix --- apps/openmw/mwmechanics/aicombat.cpp | 12 ++++++------ apps/openmw/mwmechanics/aisequence.cpp | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 0a64abec32..3054a9456c 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -2,12 +2,12 @@ #include "movement.hpp" -#include "..\mwworld\class.hpp" -#include "..\mwworld\player.hpp" -#include "..\mwworld\timestamp.hpp" -#include "..\mwbase\world.hpp" -#include "..\mwbase\environment.hpp" -#include "..\mwbase\mechanicsmanager.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/player.hpp" +#include "../mwworld/timestamp.hpp" +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "OgreMath.h" diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index bdde23e0d3..994a7a9323 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -10,14 +10,14 @@ #include "aiactivate.hpp" #include "aicombat.hpp" -#include "..\mwworld\class.hpp" +#include "../mwworld/class.hpp" #include "creaturestats.hpp" #include "npcstats.hpp" -#include "..\mwbase\environment.hpp" -#include "..\mwbase\world.hpp" -#include "..\mwworld\player.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" +#include "../mwworld/player.hpp" -#include "..\mwbase\mechanicsmanager.hpp" +#include "../mwbase/mechanicsmanager.hpp" void MWMechanics::AiSequence::copy (const AiSequence& sequence) { From 0141526c557981e013816d982823bdae51bb9bc5 Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Thu, 31 Oct 2013 10:21:10 +0100 Subject: [PATCH 149/434] Corrected tables.tex so it will compile. --- manual/opencs/filters.tex | 3 +-- manual/opencs/tables.tex | 20 ++++++-------------- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/manual/opencs/filters.tex b/manual/opencs/filters.tex index 418ead3fc5..14181f68c8 100644 --- a/manual/opencs/filters.tex +++ b/manual/opencs/filters.tex @@ -68,10 +68,9 @@ Since majority of the columns contain string values, string is among the most of \end{itemize} This is probably enough to create around 90\% string filters you will eventually need. However, this expression is even more powerful -- it accepts regular expressions (also called regexps). Regular expressions is a way to create string criteria that will be matched by one than just one specific value in the column. For instance, you can display both left and right gauntlets with the following expression: ``string("armor type", ".* gauntlet"))`` because ''.*'' in regexps means just: ``anything''. This filter says: please, show me ``any'' gauntlet. There are left and right gauntlets in the morrowind so this will evaluate to true for both. Simple, isn't it? \\ -Creating regexps can be a difficult and annoying -- especially when you need complex criteria. On the other hand, We are under impression that in reality complex expressions are needed only in sporadic cases. In fact, the truth is: that most of the time only already mentioned ``.*'' is needed and therefore the following description of regexps can be skipped by vast majority of readers. +Creating regexps can be a difficult and annoying -- especially when you need complex criteria. On the other hand, We are under impression that in reality complex expressions are needed only in sporadic cases. In fact, the truth is: that most of the time only already mentioned ``.*'' is needed and therefore the following description of regexps can be skipped by vast majority of readers.\\ %TO-DO: write the regexps essentials. -\\ Regular expressions are not the main topic of this manual. If you wish to learn more on this subject please, read the documentation on Qt regular expressions syntax, or TRE regexp syntax (it is almost like in Qt). \paragraph{Value -- value(``value'', (``open'', ``close''))} diff --git a/manual/opencs/tables.tex b/manual/opencs/tables.tex index 1a9b362d1b..fb6d41ba8d 100644 --- a/manual/opencs/tables.tex +++ b/manual/opencs/tables.tex @@ -3,8 +3,6 @@ \subsection{Introduction} If you have launched OpenCS already and played around with it for a bit, you have probably gotten the impression that it contains lots of tables. You'd be spot on: OpenCS is built around using tables. This doesn't mean it works just like Excel or Calc, though. Due to the vast amounts of information involved with Morrowind, tables just made the most sense. You have to be able to spot information quickly and be able to change them on the fly. Let's browse through the various screens and see what all these tables show. - - \subsection{Used Terms} \subsubsection{Glossary} @@ -20,18 +18,13 @@ If you have launched OpenCS already and played around with it for a bit, you hav Some columns are recurring throughout OpenCS. They show up in (nearly) every table in OpenCS. \begin{description} - \item[ID]: Each item, location, sound, etc. gets the same unique identifier in both OpenCS and Morrowind. This is usually a very self-explanatory name. For example, the ID for the (unique) black pants of Caius Cosades is "Caius_pants". This allows you to manipulate the game in many ways. For example, you could add these pants to your inventory by simply opening the console and write: player->addItem Caius_pants. Either way, in both Morrowind and OpenCS, the ID is the primary way to identify all these different parts of the game. - - \item[Modified]: This column shows what has happened (if something has happened) to this record. There are four possible states in which it can exist. - \begin{description} - \item[Base] means that this record is part of the base game and is in its original state. Usually, if you create a mod, the base game is Morrowind with optionally the Bloodmoon and Tribunal expansions. - \item[Added] means that this record was not in the base game and has been added by a modder. - \item[Modified] means that the record is part of the base game, but has been changed in some way. - \item[Deleted] means that this record used to be part of the base game, but has been removed as an entry. This does not mean, however, that the occurrences in the game itself have been removed! For example, if you remove the CharGen_Bed entry from morrowind.esm, it doesn't mean the bedroll in the basement of the Census and Excise Office in Seyda Neen is gone. You're going to have to delete that reference yourself or make sure that that object is replaced by something that still exists otherwise you'll get crashes in the worst case scenario. +\item[ID] Each item, location, sound, etc. gets the same unique identifier in both OpenCS and Morrowind. This is usually a very self-explanatory name. For example, the ID for the (unique) black pants of Caius Cosades is ``Caius\_pants''. This allows you to manipulate the game in many ways. For example, you could add these pants to your inventory by simply opening the console and write: ``player->addItem Caius\_pants''. Either way, in both Morrowind and OpenCS, the ID is the primary way to identify all these different parts of the game. %Wrong! Cells do not have ID, only name. +\item[Modified] This column shows what has happened (if something has happened) to this record. There are four possible states in which it can exist. +\item[Base] means that this record is part of the base game and is in its original state. Usually, if you create a mod, the base game is Morrowind with optionally the Bloodmoon and Tribunal expansions. +\item[Added] means that this record was not in the base game and has been added by a modder. +\item[Modified] means that the record is part of the base game, but has been changed in some way. +\item[Deleted] means that this record used to be part of the base game, but has been removed as an entry. This does not mean, however, that the occurrences in the game itself have been removed! For example, if you remove the CharGen\_Bed entry from morrowind.esm, it doesn't mean the bedroll in the basement of the Census and Excise Office in Seyda Neen is gone. You're going to have to delete that reference yourself or make sure that that object is replaced by something that still exists otherwise you'll get crashes in the worst case scenario. \end{description} -\end{description} - - \subsection{World Screens} @@ -78,5 +71,4 @@ Let's go through all Record Types and discuss what you can tell OpenCS about the \begin{description} \item[Activator:] This is an item that, when activated, starts a script or even just shows a tooltip. - \end{description} \end{description} \ No newline at end of file From 683b8d77dd3bb7f83b31f870f6e07e732291a818 Mon Sep 17 00:00:00 2001 From: gus Date: Thu, 31 Oct 2013 11:02:26 +0100 Subject: [PATCH 150/434] build fix (thanks travis) --- apps/openmw/mwmechanics/aicombat.cpp | 3 +++ apps/openmw/mwmechanics/aicombat.hpp | 8 -------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 3054a9456c..aeddb4781e 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -9,6 +9,9 @@ #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "creaturestats.hpp" +#include "npcstats.hpp" + #include "OgreMath.h" namespace diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 7321b2cde6..eed92ef3a7 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -5,14 +5,6 @@ #include "pathfinding.hpp" - -#include "..\mwworld\class.hpp" -#include "creaturestats.hpp" -#include "npcstats.hpp" -#include "..\mwbase\environment.hpp" -#include "..\mwbase\world.hpp" -#include "..\mwworld\player.hpp" - #include "movement.hpp" namespace MWMechanics From 69f28ee4bebdb636b3623c7342f1c72d98532858 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 31 Oct 2013 12:16:45 +0100 Subject: [PATCH 151/434] split info records between journal and topic info tables --- apps/opencs/model/world/data.cpp | 24 ++++++++++++++++++++-- apps/opencs/model/world/infocollection.cpp | 2 +- apps/opencs/model/world/infocollection.hpp | 7 ++++++- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 2947b2b8fc..0962c3856b 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -456,6 +456,8 @@ void CSMWorld::Data::loadFile (const boost::filesystem::path& path, bool base) reader.open (path.string()); + const ESM::Dialogue *dialogue = 0; + // Note: We do not need to send update signals here, because at this point the model is not connected // to any view. while (reader.hasMoreRecs()) @@ -516,10 +518,15 @@ void CSMWorld::Data::loadFile (const boost::filesystem::path& path, bool base) if (record.mType==ESM::Dialogue::Journal) { + int index = mJournals.getAppendIndex (id); mJournals.load (record, base); + dialogue = &mJournals.getRecord (index).get(); } else if (record.mType==ESM::Dialogue::Deleted) { + dialogue = 0; // record vector can be shuffled around which would make pointer + // to record invalid + if (mJournals.tryDelete (id)) { /// \todo handle info records @@ -535,7 +542,9 @@ void CSMWorld::Data::loadFile (const boost::filesystem::path& path, bool base) } else { + int index = mTopics.getAppendIndex (id); mTopics.load (record, base); + dialogue = &mTopics.getRecord (index).get(); } break; @@ -543,14 +552,25 @@ void CSMWorld::Data::loadFile (const boost::filesystem::path& path, bool base) case ESM::REC_INFO: { - /// \todo associate info record with last loaded dialogue record - mJournalInfos.load (reader, base); + if (!dialogue) + { + /// \todo INFO record without matching DIAL record -> report to user + reader.skipRecord(); + break; + } + + if (dialogue->mType==ESM::Dialogue::Journal) + mJournalInfos.load (reader, base, *dialogue); + else + mTopicInfos.load (reader, base, *dialogue); + break; } default: /// \todo throw an exception instead, once all records are implemented + /// or maybe report error and continue? reader.skipRecord(); } } diff --git a/apps/opencs/model/world/infocollection.cpp b/apps/opencs/model/world/infocollection.cpp index 858f788fa5..42425ecf57 100644 --- a/apps/opencs/model/world/infocollection.cpp +++ b/apps/opencs/model/world/infocollection.cpp @@ -30,7 +30,7 @@ void CSMWorld::InfoCollection::load (const ESM::DialInfo& record, bool base) } } -void CSMWorld::InfoCollection::load (ESM::ESMReader& reader, bool base) +void CSMWorld::InfoCollection::load (ESM::ESMReader& reader, bool base, const ESM::Dialogue& dialogue) { /// \todo put records into proper order /// \todo adjust ID diff --git a/apps/opencs/model/world/infocollection.hpp b/apps/opencs/model/world/infocollection.hpp index 8dca2b2190..8bb8e53098 100644 --- a/apps/opencs/model/world/infocollection.hpp +++ b/apps/opencs/model/world/infocollection.hpp @@ -5,6 +5,11 @@ #include "collection.hpp" +namespace ESM +{ + class Dialogue; +} + namespace CSMWorld { class InfoCollection : public Collection > @@ -13,7 +18,7 @@ namespace CSMWorld public: - void load (ESM::ESMReader& reader, bool base); + void load (ESM::ESMReader& reader, bool base, const ESM::Dialogue& dialogue); }; } From 767cb54e7c1b1271518b4bba713d4817b3375d82 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 31 Oct 2013 12:54:55 +0100 Subject: [PATCH 152/434] added missing columns to journal info table --- apps/opencs/model/world/columnbase.hpp | 3 +- apps/opencs/model/world/columnimp.hpp | 77 ++++++++++++++++++++++++++ apps/opencs/model/world/columns.cpp | 9 +++ apps/opencs/model/world/columns.hpp | 3 + apps/opencs/model/world/data.cpp | 3 + apps/opencs/view/doc/viewmanager.cpp | 3 +- components/esm/loadinfo.hpp | 8 +-- 7 files changed, 100 insertions(+), 6 deletions(-) diff --git a/apps/opencs/model/world/columnbase.hpp b/apps/opencs/model/world/columnbase.hpp index 9b8d7dafb8..2c3408a013 100644 --- a/apps/opencs/model/world/columnbase.hpp +++ b/apps/opencs/model/world/columnbase.hpp @@ -44,7 +44,8 @@ namespace CSMWorld Display_WeaponType, Display_RecordState, Display_RefRecordType, - Display_DialogueType + Display_DialogueType, + Display_QuestStatusType }; int mColumnId; diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index 23c529b3e3..cddfafb632 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -1317,6 +1317,83 @@ namespace CSMWorld return false; } }; + + template + struct QuestStatusTypeColumn : public Column + { + QuestStatusTypeColumn() + : Column (Columns::ColumnId_QuestStatusType, ColumnBase::Display_QuestStatusType) + {} + + virtual QVariant get (const Record& record) const + { + return static_cast (record.get().mQuestStatus); + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + + record2.mQuestStatus = static_cast (data.toInt()); + + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + }; + + template + struct QuestDescriptionColumn : public Column + { + QuestDescriptionColumn() : Column (Columns::ColumnId_QuestDescription, ColumnBase::Display_String) {} + + virtual QVariant get (const Record& record) const + { + return QString::fromUtf8 (record.get().mResponse.c_str()); + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + + record2.mResponse = data.toString().toUtf8().constData(); + + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + }; + + template + struct QuestIndexColumn : public Column + { + QuestIndexColumn() + : Column (Columns::ColumnId_QuestIndex, ColumnBase::Display_Integer) + {} + + virtual QVariant get (const Record& record) const + { + return record.get().mData.mDisposition; + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + record2.mData.mDisposition = data.toInt(); + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + }; } #endif diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index ae4136bd92..307862b1bb 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -160,6 +160,9 @@ namespace CSMWorld { ColumnId_DoorPositionYRot, "Teleport Rot Y" }, { ColumnId_DoorPositionZRot, "Teleport Rot Z" }, { ColumnId_DialogueType, "Dialogue Type" }, + { ColumnId_QuestIndex, "Quest Index" }, + { ColumnId_QuestStatusType, "Quest Status" }, + { ColumnId_QuestDescription, "Quest Description" }, { ColumnId_UseValue1, "Use value 1" }, { ColumnId_UseValue2, "Use value 2" }, @@ -275,6 +278,11 @@ namespace "Topic", "Voice", "Greeting", "Persuasion", 0 }; + static const char *sQuestStatusTypes[] = + { + "None", "Name", "Finished", "Restart", 0 + }; + const char **getEnumNames (CSMWorld::Columns::ColumnId column) { switch (column) @@ -290,6 +298,7 @@ namespace case CSMWorld::Columns::ColumnId_Modification: return sModificationEnums; case CSMWorld::Columns::ColumnId_ValueType: return sVarTypeEnums; case CSMWorld::Columns::ColumnId_DialogueType: return sDialogueTypeEnums; + case CSMWorld::Columns::ColumnId_QuestStatusType: return sQuestStatusTypes; default: return 0; } diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index 111931fa85..d7b96cab6b 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -153,6 +153,9 @@ namespace CSMWorld ColumnId_DoorPositionYRot = 140, ColumnId_DoorPositionZRot = 141, ColumnId_DialogueType = 142, + ColumnId_QuestIndex = 143, + ColumnId_QuestStatusType = 144, + ColumnId_QuestDescription = 145, // Allocated to a separate value range, so we don't get a collision should we ever need // to extend the number of use values. diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 0962c3856b..fc81287fee 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -154,6 +154,9 @@ CSMWorld::Data::Data() : mRefs (mCells) mJournalInfos.addColumn (new StringIdColumn); mJournalInfos.addColumn (new RecordStateColumn); + mJournalInfos.addColumn (new QuestStatusTypeColumn); + mJournalInfos.addColumn (new QuestIndexColumn); + mJournalInfos.addColumn (new QuestDescriptionColumn); mCells.addColumn (new StringIdColumn); mCells.addColumn (new RecordStateColumn); diff --git a/apps/opencs/view/doc/viewmanager.cpp b/apps/opencs/view/doc/viewmanager.cpp index a4849795b2..b4b55dea0d 100644 --- a/apps/opencs/view/doc/viewmanager.cpp +++ b/apps/opencs/view/doc/viewmanager.cpp @@ -75,7 +75,8 @@ CSVDoc::ViewManager::ViewManager (CSMDoc::DocumentManager& documentManager) { CSMWorld::ColumnBase::Display_ClothingType, CSMWorld::Columns::ColumnId_ClothingType, false }, { CSMWorld::ColumnBase::Display_CreatureType, CSMWorld::Columns::ColumnId_CreatureType, false }, { CSMWorld::ColumnBase::Display_WeaponType, CSMWorld::Columns::ColumnId_WeaponType, false }, - { CSMWorld::ColumnBase::Display_DialogueType, CSMWorld::Columns::ColumnId_DialogueType, false } + { CSMWorld::ColumnBase::Display_DialogueType, CSMWorld::Columns::ColumnId_DialogueType, false }, + { CSMWorld::ColumnBase::Display_QuestStatusType, CSMWorld::Columns::ColumnId_QuestStatusType, false } }; for (std::size_t i=0; i Date: Thu, 31 Oct 2013 13:11:15 +0100 Subject: [PATCH 153/434] fixed dialogue record loading with multiple content files --- apps/opencs/model/world/data.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index fc81287fee..d068403701 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -521,9 +521,8 @@ void CSMWorld::Data::loadFile (const boost::filesystem::path& path, bool base) if (record.mType==ESM::Dialogue::Journal) { - int index = mJournals.getAppendIndex (id); mJournals.load (record, base); - dialogue = &mJournals.getRecord (index).get(); + dialogue = &mJournals.getRecord (id).get(); } else if (record.mType==ESM::Dialogue::Deleted) { @@ -545,9 +544,8 @@ void CSMWorld::Data::loadFile (const boost::filesystem::path& path, bool base) } else { - int index = mTopics.getAppendIndex (id); mTopics.load (record, base); - dialogue = &mTopics.getRecord (index).get(); + dialogue = &mTopics.getRecord (id).get(); } break; From ea0e8be0d337111d25e78824d451c70c58419380 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 31 Oct 2013 13:40:14 +0100 Subject: [PATCH 154/434] disallow sorting (via column headers) in info tables --- apps/opencs/view/doc/subviewfactoryimp.hpp | 11 ++++++++++- apps/opencs/view/world/subviews.cpp | 8 ++++++-- apps/opencs/view/world/table.cpp | 4 ++-- apps/opencs/view/world/table.hpp | 3 ++- apps/opencs/view/world/tablesubview.cpp | 4 ++-- apps/opencs/view/world/tablesubview.hpp | 2 +- 6 files changed, 23 insertions(+), 9 deletions(-) diff --git a/apps/opencs/view/doc/subviewfactoryimp.hpp b/apps/opencs/view/doc/subviewfactoryimp.hpp index 2c4158f854..059b24fd0f 100644 --- a/apps/opencs/view/doc/subviewfactoryimp.hpp +++ b/apps/opencs/view/doc/subviewfactoryimp.hpp @@ -26,16 +26,25 @@ namespace CSVDoc template class SubViewFactoryWithCreator : public SubViewFactoryBase { + bool mSorting; + public: + SubViewFactoryWithCreator (bool sorting = true); + virtual CSVDoc::SubView *makeSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document); }; + template + SubViewFactoryWithCreator::SubViewFactoryWithCreator (bool sorting) + : mSorting (sorting) + {} + template CSVDoc::SubView *SubViewFactoryWithCreator::makeSubView ( const CSMWorld::UniversalId& id, CSMDoc::Document& document) { - return new SubViewT (id, document, CreatorFactoryT()); + return new SubViewT (id, document, CreatorFactoryT(), mSorting); } } diff --git a/apps/opencs/view/world/subviews.cpp b/apps/opencs/view/world/subviews.cpp index 66e5806baf..85972e7f00 100644 --- a/apps/opencs/view/world/subviews.cpp +++ b/apps/opencs/view/world/subviews.cpp @@ -37,8 +37,6 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) CSMWorld::UniversalId::Type_Regions, CSMWorld::UniversalId::Type_Birthsigns, CSMWorld::UniversalId::Type_Spells, - CSMWorld::UniversalId::Type_TopicInfos, - CSMWorld::UniversalId::Type_JournalInfos, CSMWorld::UniversalId::Type_None // end marker }; @@ -62,6 +60,12 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) manager.add (CSMWorld::UniversalId::Type_Journal, new CSVDoc::SubViewFactoryWithCreator); + manager.add (CSMWorld::UniversalId::Type_TopicInfos, + new CSVDoc::SubViewFactoryWithCreator > (false)); + + manager.add (CSMWorld::UniversalId::Type_JournalInfos, + new CSVDoc::SubViewFactoryWithCreator > (false)); + // Subviews for editing/viewing individual records manager.add (CSMWorld::UniversalId::Type_Script, new CSVDoc::SubViewFactory); diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index a58eb873f3..dfdfa6d13e 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -121,7 +121,7 @@ std::vector CSVWorld::Table::listDeletableSelectedIds() const } CSVWorld::Table::Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, QUndoStack& undoStack, - bool createAndDelete) + bool createAndDelete, bool sorting) : mUndoStack (undoStack), mCreateAction (0), mEditLock (false), mRecordStatusDisplay (0) { mModel = &dynamic_cast (*data.getTableModel (id)); @@ -132,7 +132,7 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, Q setModel (mProxyModel); horizontalHeader()->setResizeMode (QHeaderView::Interactive); verticalHeader()->hide(); - setSortingEnabled (true); + setSortingEnabled (sorting); setSelectionBehavior (QAbstractItemView::SelectRows); setSelectionMode (QAbstractItemView::ExtendedSelection); diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp index d931090563..3e42144248 100644 --- a/apps/opencs/view/world/table.hpp +++ b/apps/opencs/view/world/table.hpp @@ -49,8 +49,9 @@ namespace CSVWorld public: - Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, QUndoStack& undoStack, bool createAndDelete); + Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, QUndoStack& undoStack, bool createAndDelete, bool sorting); ///< \param createAndDelete Allow creation and deletion of records. + /// \param sorting Allow changing order of rows in the view via column headers. void setEditLock (bool locked); diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp index 1e05fbf51c..55ded09de2 100644 --- a/apps/opencs/view/world/tablesubview.cpp +++ b/apps/opencs/view/world/tablesubview.cpp @@ -12,7 +12,7 @@ #include "creator.hpp" CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, - const CreatorFactoryBase& creatorFactory) + const CreatorFactoryBase& creatorFactory, bool sorting) : SubView (id) { QVBoxLayout *layout = new QVBoxLayout; @@ -23,7 +23,7 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D new TableBottomBox (creatorFactory, document.getData(), document.getUndoStack(), id, this), 0); layout->insertWidget (0, mTable = - new Table (id, document.getData(), document.getUndoStack(), mBottom->canCreateAndDelete()), 2); + new Table (id, document.getData(), document.getUndoStack(), mBottom->canCreateAndDelete(), sorting), 2); CSVFilter::FilterBox *filterBox = new CSVFilter::FilterBox (document.getData(), this); diff --git a/apps/opencs/view/world/tablesubview.hpp b/apps/opencs/view/world/tablesubview.hpp index d61c789356..56a441a4d5 100644 --- a/apps/opencs/view/world/tablesubview.hpp +++ b/apps/opencs/view/world/tablesubview.hpp @@ -26,7 +26,7 @@ namespace CSVWorld public: TableSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, - const CreatorFactoryBase& creatorFactory); + const CreatorFactoryBase& creatorFactory, bool sorting); virtual void setEditLock (bool locked); From a85dffe644b039a9badbdb6126c5609bc4479eb2 Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Thu, 31 Oct 2013 20:28:01 +0300 Subject: [PATCH 155/434] Delete README_Mac.md Use updated manual from wiki: https://wiki.openmw.org/index.php?title=Development_Environment_Setup#Mac_OS_X --- README_Mac.md | 160 -------------------------------------------------- 1 file changed, 160 deletions(-) delete mode 100644 README_Mac.md diff --git a/README_Mac.md b/README_Mac.md deleted file mode 100644 index dc39183680..0000000000 --- a/README_Mac.md +++ /dev/null @@ -1,160 +0,0 @@ -#Getting OpenMW Working on OS X - -## Initial setup -First of all, clone OpenMW repo. - - $ git clone github.com/zinnschlag/openmw - -Or use your github url if you forked. - -About dependencies: I prefer not to install them globally (i. e. in /usr/local/), so I'm installing them in directory in my home directory. If OpenMW sources is in $HOME/path/openmw, I'm using $HOME/path/libs/root as prefix for boost and other libs. - -It's useful to create env var for lib install prefix: - - $ export OMW_LIB_PREFIX=$HOME/path/libs/root` - -Most of libs can be installed from [Homebrew][homebrew]. Only mpg123 needs to be installed from source (due to lack of universal compilation support). I think that some of libs can be installed from MacPorts or Fink too. - -As OpenMW currently only supports i386 architecture on OS X, denendencies also should support it. Set some env vars in current terminal: - - $ export CFLAGS="-arch i386" - $ export CXXFLAGS="-arch i386" - $ export LDFLAGS="-arch i386" - -If you close your terminal, you should set env vars again before pcoceeding to next steps! - -## Boost -Download [boost][boost] and install it with the following command: - - $ cd /path/to/boost/source - $ ./bootstrap.sh --prefix=$OMW_LIB_PREFIX - $ ./bjam --build-dir=build --layout=versioned \ - --toolset=darwin architecture=x86 address-model=32 \ - --link-shared,static --prefix=$OMW_LIB_PREFIX install - - -Alternatively you can install boost with homebrew: - - $ brew install boost --universal - -I think MacPorts also should support universal build for boost. - -## Ogre -Download [Ogre][] SDK (tested with 1.7.3), unpack it somewhere and move -`lib/Release/Ogre.framework` into `/Library/Frameworks`. - -## OIS -Download patched [OIS][] and use the XCode project provided. Be sure to set your build architecture to - `i386`. Once it built, locate built OIS.framework with Xcode and move it to `/Library/Frameworks`. - -## mpg123 -Download [MPG 123][mpg123] and build it: - - $ cd /path/to/mpg123/source - $ ./configure --prefix=$OMW_LIB_PREFIX --disable-debug \ - --disable-dependency-tracking \ - --with-optimization=4 \ - --with-audio=dummy \ - --with-default-audio=dummy \ - --with-cpu=sse_alone \ - $ make install - -## libsndfile -Download [libsndfile][] and build it: - - $ cd /path/to/libsndfile/source - $ ./configure --prefix=$OMW_LIB_PREFIX \ - --disable-dependency-tracking - $ make install - -or install with homebrew: - - $ brew install libsndfile --universal - -## Bullet -Download [Bullet][] and build it: - - $ cd /path/to/bullet/source - $ mkdir build - $ cd build - $ cmake -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_INSTALL_PREFIX=$OMW_LIB_PREFIX \ - -DBUILD_EXTRAS=OFF \ - -DBUILD_DEMOS=OFF \ - -DCMAKE_OSX_ARCHITECTURES=i386 \ - -DCMAKE_INSTALL_NAME_DIR=$OMW_LIB_RPEFIX/lib \ - -G"Unix Makefiles" ../ - $ make install - -or install with homebrew: - - $ brew install bullet --HEAD --universal - -I prefer head because 2.79 has some issue which causes OpenMW to lag. Also you can edit formula and install 2.77, which is stable and haven't mentioned issue. - -## Qt -Install [Qt][qt]. Qt SDK distributed by Nokia is not an option because it's 64 bit only, and OpenMW currently doesn't build for 64 bit on OS X. I'm installing it from Homebrew: - - $ brew install qt --universal - -## Run CMake -Generate the Makefile for OpenMW as follows and build OpenMW: - - $ mkdir /path/to/openmw/build/dir - $ cd /path/to/open/build/dir - $ cmake \ - -D CMAKE_OSX_ARCHITECTURES=i386 \ - -D OGRE_SDK=/path/to/ogre/sdk \ - -D BOOST_INCLUDEDIR=$OMW_LIB_PREFIX/include/boost-1_45 \ - -D BOOST_LIBRARYDIR=$OMW_LIB_PREFIX/lib \ - -D SNDFILE_INCLUDE_DIR=$OMW_LIB_PREFIX/include \ - -D SNDFILE_LIBRARY=$OMW_LIB_PREFIX/lib/libsndfile.a \ - -D MPG123_LIBRARY=$OMW_LIB_PREFIX/lib/libmpg123.a \ - -D MPG123_INCLUDE_DIR=$OMW_LIB_PREFIX/include \ - -D BULLET_DYNAMICS_LIBRARY=$OMW_LIB_PREFIX/lib/libBulletDynamics.a \ - -D BULLET_COLLISION_LIBRARY=$OMW_LIB_PREFIX/lib/libBulletCollision.a \ - -D BULLET_MATH_LIBRARY=$OMW_LIB_PREFIX/lib/libLinearMath.a \ - -D BULLET_SOFTBODY_LIBRARY=$OMW_LIB_PREFIX/lib/libBulletSoftBody.a \ - -D BULLET_INCLUDE_DIR=$OMW_LIB_PREFIX/include/bullet/ \ - -G "Unix Makefiles" /path/to/openmw/source/dir - $ make - -You can use `-G"Xcode"` if you prefer Xcode, or -G"Eclipse CDT4 - Unix Makefiles" -if you prefer Eclipse. You also can specify `-D CMAKE_BUILD_TYPE=Debug` for debug -build. As for CMake 2.8.7 and Xcode 4.3, Xcode generator is broken. Sadly Eclipse CDT also cannot import generated project at least on my machine. - -If all libs installed via homebrew (excluding mpg123), then command would be even simplier: - - $ cmake \ - -D CMAKE_OSX_ARCHITECTURES="i386" \ - -D OGRE_SDK=/path/to/ogre/sdk \ - -D MPG123_LIBRARY=$OMW_LIB_PREFIX/lib/libmpg123.a \ - -D MPG123_INCLUDE_DIR=$OMW_LIB_PREFIX/include \ - -G "Unix Makefiles" /path/to/openmw/source/dir - $ make - -Note for users with recent Xcode versions: you must explicitly specify what set of compilers do you use! If not, gcc will be used for C and Clang for C++. Just add this two -D's to command: `-D CMAKE_C_COMPILER=/usr/bin/clang` and `-D CMAKE_CXX_COMPILER=/usr/bin/clang` - -Note for Xcode 4.3 users: you should specify full path to used SDK, because current CMake (2.8.7) couldn't find SDKs inside Xcode app bundle: - - -D CMAKE_OSX_SYSROOT="/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk" - -# Run -From your build directory run: - - $ OpenMW.app/Contents/MacOS/openmw -or: - - $ open OpenMW.app -Enjoy! - -[homebrew]: https://github.com/mxcl/homebrew -[boost]: http://www.boost.org -[Ogre]: http://www.ogre3d.org -[Bullet]: http://bulletphysics.org -[OIS]: https://github.com/corristo/ois-fork -[mpg123]: http://www.mpg123.de -[libsndfile]: http://www.mega-nerd.com/libsndfile -[official website]: http://openmw.com -[Will Thimbleby's Ogre Framework]: http://www.thimbleby.net/ogre/ -[qt]: http://qt.nokia.com/ \ No newline at end of file From 0cb591e4f61a3f2caeb84cfc73c0b68ff187a244 Mon Sep 17 00:00:00 2001 From: graffy76 Date: Thu, 31 Oct 2013 18:12:13 -0500 Subject: [PATCH 156/434] Fixed path problem with adjuster widget and local data path. --- apps/opencs/editor.cpp | 7 ++--- apps/opencs/view/doc/adjusterwidget.cpp | 35 ++++++++++++++++--------- apps/opencs/view/doc/adjusterwidget.hpp | 2 ++ apps/opencs/view/doc/filedialog.cpp | 18 ++++++------- 4 files changed, 35 insertions(+), 27 deletions(-) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 1c861ed67e..63afe7a9dd 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -10,8 +10,6 @@ #include "model/world/data.hpp" #include -#include - CS::Editor::Editor() : mDocumentManager (mCfgMgr), mViewManager (mDocumentManager) { @@ -119,13 +117,13 @@ void CS::Editor::createGame() void CS::Editor::createAddon() { mStartup.hide(); - mFileDialog.showDialog (CSVDoc::FileDialog::DialogType_New); + mFileDialog.showDialog (CSVDoc::ContentAction_New); } void CS::Editor::loadDocument() { mStartup.hide(); - mFileDialog.showDialog (CSVDoc::FileDialog::DialogType_Open); + mFileDialog.showDialog (CSVDoc::ContentAction_Edit); } void CS::Editor::openFiles (const boost::filesystem::path &savePath) @@ -135,7 +133,6 @@ void CS::Editor::openFiles (const boost::filesystem::path &savePath) foreach (const QString &path, mFileDialog.selectedFilePaths()) files.push_back(path.toStdString()); - qDebug() << "save file path: " << savePath.c_str(); CSMDoc::Document *document = mDocumentManager.addDocument (files, savePath, false); mViewManager.addView (document); diff --git a/apps/opencs/view/doc/adjusterwidget.cpp b/apps/opencs/view/doc/adjusterwidget.cpp index 332d460758..09e58690fc 100644 --- a/apps/opencs/view/doc/adjusterwidget.cpp +++ b/apps/opencs/view/doc/adjusterwidget.cpp @@ -52,47 +52,56 @@ bool CSVDoc::AdjusterWidget::isValid() const { return mValid; } + +void CSVDoc::AdjusterWidget::setFilenameCheck (bool doCheck) +{ + mDoFilenameCheck = doCheck; +} + void CSVDoc::AdjusterWidget::setName (const QString& name, bool addon) { QString message; - if (mAction == ContentAction_Undefined) - { - throw std::runtime_error("ContentAction_Undefined when AdjusterWidget::setName() called."); - return; - } + mValid = (!name.isEmpty()); - if (name.isEmpty()) + if (!mValid) { - mValid = false; message = "No name."; } else { boost::filesystem::path path (name.toUtf8().data()); - path.replace_extension (addon ? ".omwaddon" : ".omwgame"); + bool isLegacyPath = (path.extension() == ".esm" || + path.extension() == ".esp"); - if (path.parent_path().string()==mLocalData.string()) + bool isFilePathChanged = (path.parent_path().string() != mLocalData.string()); + + if (isLegacyPath) + path.replace_extension (addon ? ".omwaddon" : ".omwgame"); + + //if the file came from data-local and is not a legacy file to be converted, + //don't worry about doing a file check. + if (!isFilePathChanged && !isLegacyPath) { // path already points to the local data directory message = QString::fromUtf8 (("Will be saved as: " + path.string()).c_str()); mResultPath = path; - mValid = true; } + //in all other cases, ensure the path points to data-local and do an existing file check else { // path points somewhere else or is a leaf name. - path = mLocalData / path.filename(); + if (isFilePathChanged) + path = mLocalData / path.filename(); message = QString::fromUtf8 (("Will be saved as: " + path.string()).c_str()); mResultPath = path; - mValid = true; if (boost::filesystem::exists (path)) { /// \todo add an user setting to make this an error. - message += "

But a file with the same name already exists. If you continue, it will be overwritten."; + message += "

A file with the same name already exists. If you continue, it will be overwritten."; } } } diff --git a/apps/opencs/view/doc/adjusterwidget.hpp b/apps/opencs/view/doc/adjusterwidget.hpp index 627f89c1f8..91e308236f 100644 --- a/apps/opencs/view/doc/adjusterwidget.hpp +++ b/apps/opencs/view/doc/adjusterwidget.hpp @@ -28,6 +28,7 @@ namespace CSVDoc bool mValid; boost::filesystem::path mResultPath; ContentAction mAction; + bool mDoFilenameCheck; public: @@ -36,6 +37,7 @@ namespace CSVDoc void setLocalData (const boost::filesystem::path& localData); void setAction (ContentAction action); + void setFilenameCheck (bool doCheck); bool isValid() const; boost::filesystem::path getPath() const; diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index 57b61ff8b2..c0cda26dc9 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -17,8 +17,6 @@ #include "filewidget.hpp" #include "adjusterwidget.hpp" -#include - CSVDoc::FileDialog::FileDialog(QWidget *parent) : QDialog(parent), mSelector (0), mFileWidget (0), mAdjusterWidget (0) { @@ -47,9 +45,6 @@ QStringList CSVDoc::FileDialog::selectedFilePaths() void CSVDoc::FileDialog::setLocalData (const boost::filesystem::path& localData) { - if (mDialogType != DialogType_New) - return; - mAdjusterWidget->setLocalData (localData); } @@ -68,10 +63,13 @@ void CSVDoc::FileDialog::showDialog (ContentAction action) case ContentAction_Edit: buildOpenFileView(); break; + default: break; } + mAdjusterWidget->setFilenameCheck (mAction == ContentAction_New); + //connections common to both dialog view flavors connect (mSelector, SIGNAL (signalCurrentGamefileIndexChanged (int)), this, SLOT (slotUpdateAcceptButton (int))); @@ -124,7 +122,7 @@ void CSVDoc::FileDialog::slotUpdateAcceptButton (int) { QString name = ""; - if (mDialogType == DialogType_New) + if (mAction == ContentAction_New) name = mFileWidget->getName(); slotUpdateAcceptButton (name, true); @@ -134,11 +132,13 @@ void CSVDoc::FileDialog::slotUpdateAcceptButton(const QString &name, bool) { bool success = (mSelector->selectedFiles().size() > 0); - if (mDialogType == DialogType_New) + bool isNew = (mAction == ContentAction_New); + + if (isNew) success = success && !(name.isEmpty()); else { - ContentSelectorModel::EsmFile *file = mSelector->selectedFiles().back(); + ContentSelectorModel::EsmFile *file = mSelector->selectedFiles().back();; mAdjusterWidget->setName (file->fileName(), !file->isGameFile()); } @@ -147,7 +147,7 @@ void CSVDoc::FileDialog::slotUpdateAcceptButton(const QString &name, bool) QString CSVDoc::FileDialog::filename() const { - if (mDialogType == DialogType_New) + if (mAction == ContentAction_New) return ""; return mSelector->currentFile(); From 8ae38eaa1f03699b50c65548e318ac474f064cf9 Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Fri, 1 Nov 2013 13:52:24 +0100 Subject: [PATCH 157/434] Corrected few things and added short description for creating and saving filter, as well as replacing the default filters set. --- manual/opencs/filters.tex | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/manual/opencs/filters.tex b/manual/opencs/filters.tex index 14181f68c8..8f4469fc79 100644 --- a/manual/opencs/filters.tex +++ b/manual/opencs/filters.tex @@ -45,7 +45,7 @@ We strongly recommend to take a look at the filters table right now to see what Back to the manual? Great.\\ If you want to create your own filter you have to know exactly what do you want to get in order to translate this into the expressions. Finally, you will have to write this with correct syntax. As a result table will show only desired rows.\\ Advance subsection covers everything that you need to know in order to create any filter you may want to. -\subsection{Namespaces} +\subsubsection{Namespaces} Did you noticed that every default filter has ``project::`` prefix? It is a ``namespace``, a term borrowed from the C++ language. In case of OpenCS namespace of the filter determinate if the filter will be stored along with your project file or if it will be forgotten as soon as OpenCS quits. \begin{description} \item[project::] namespace indicates that filter is stored inside the project file. @@ -82,7 +82,7 @@ As you would imagine the range can be specified as including a border value, or \item Mixing brackets is completely legal. For value equal 10, expression value(something, [5, 10) will evaluate to true. The same expression will evaluate to false for value equal 10. \end{itemize} -\subsection{Logical expressions} +\susubbsection{Logical expressions} This subsection takes care of two remaining groups of expressions: binary and unary. The only unary expression present in the OpenCS is logical not, while the remaining binary expressions are: or, and. This clearly makes theme from user point of view belonging to the same group of logical expressions. \paragraph{not -- not expression()} @@ -95,4 +95,12 @@ Or expression is useful when showing two different group of records is needed. F \paragraph{and -- and(expression1(), expression2())} And is a expression that will return true if all arguments evaluates to true. As in the case of ''or`` you can use two or more arguments, separated by a comma.\\ -As We mentioned earlier in the ''not`` filter, combining ''not`` with ''and`` can be very useful. For instance to show all armor types, excluding gauntlets you can write the following: ''and (not string("armor type", ".* gauntlet"), string(''Record Type``, ''Armor``))''. \ No newline at end of file +As We mentioned earlier in the ''not`` filter, combining ''not`` with ''and`` can be very useful. For instance to show all armor types, excluding gauntlets you can write the following: ''and (not string("armor type", ".* gauntlet"), string(''Record Type``, ''Armor``))''. + +\subsubsection{Creating and saving filter} +In order to create and save new filter, you should go to the filters table, right click and select option ``add record'' from the context menu. A horizontal widget group at the bottom of the table should show up. From there you should select a namespace responsible for scope of the filter (described earlier) and desired ID of the filter. After pressing ok button new entry will show up in the filters table. This filter does nothing at the moment, since it still lacks expressions. In order to add your formula simply double click the filter cell of the new entry and write it down there.\\ +Done! You are free to use your filter. + +\subsubsection{Replacing the default filters set} +{OpenCS} allows you to substitute default filters set provided by us, with your own filters. In order to do so you should create a new project, add desired filters, remove undesired and save. Rename the file to the ``defaultfilters'' (don't forget to remove .omwaddon.project extension) and place it inside your configuration directory.\\ +The file acts as template for all new project files from now. If you wish to go back to the old default set, simply rename or remove the custom file. \ No newline at end of file From 83029244642b109d1b66fcaa481a51cfb63b7fb3 Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Fri, 1 Nov 2013 13:54:34 +0100 Subject: [PATCH 158/434] corrected typo --- manual/opencs/filters.tex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manual/opencs/filters.tex b/manual/opencs/filters.tex index 8f4469fc79..f69f6d7f1b 100644 --- a/manual/opencs/filters.tex +++ b/manual/opencs/filters.tex @@ -82,7 +82,7 @@ As you would imagine the range can be specified as including a border value, or \item Mixing brackets is completely legal. For value equal 10, expression value(something, [5, 10) will evaluate to true. The same expression will evaluate to false for value equal 10. \end{itemize} -\susubbsection{Logical expressions} +\subsubsection{Logical expressions} This subsection takes care of two remaining groups of expressions: binary and unary. The only unary expression present in the OpenCS is logical not, while the remaining binary expressions are: or, and. This clearly makes theme from user point of view belonging to the same group of logical expressions. \paragraph{not -- not expression()} From 15b7d3263c2c70ee7c70dddb49769507b42f837d Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 1 Nov 2013 17:43:45 +0100 Subject: [PATCH 159/434] subclass ESM::DialInfo to keep track of parent topic --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/model/world/columnimp.hpp | 3 ++- apps/opencs/model/world/data.cpp | 14 +++++++------- apps/opencs/model/world/info.hpp | 14 ++++++++++++++ apps/opencs/model/world/infocollection.cpp | 12 +++++++----- apps/opencs/model/world/infocollection.hpp | 7 +++---- 6 files changed, 34 insertions(+), 18 deletions(-) create mode 100644 apps/opencs/model/world/info.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index f4846675e0..5e6b853e86 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -28,7 +28,7 @@ opencs_units_noqt (model/world ) opencs_hdrs_noqt (model/world - columnimp idcollection collection + columnimp idcollection collection info ) diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index cddfafb632..dc80f6eeb4 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -9,6 +9,7 @@ #include "columnbase.hpp" #include "columns.hpp" +#include "info.hpp" namespace CSMWorld { @@ -1334,7 +1335,7 @@ namespace CSMWorld { ESXRecordT record2 = record.get(); - record2.mQuestStatus = static_cast (data.toInt()); + record2.mQuestStatus = static_cast (data.toInt()); record.setModified (record2); } diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index d068403701..bfec13f909 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -149,14 +149,14 @@ CSMWorld::Data::Data() : mRefs (mCells) mJournals.addColumn (new RecordStateColumn); mJournals.addColumn (new DialogueTypeColumn (true)); - mTopicInfos.addColumn (new StringIdColumn); - mTopicInfos.addColumn (new RecordStateColumn); + mTopicInfos.addColumn (new StringIdColumn); + mTopicInfos.addColumn (new RecordStateColumn); - mJournalInfos.addColumn (new StringIdColumn); - mJournalInfos.addColumn (new RecordStateColumn); - mJournalInfos.addColumn (new QuestStatusTypeColumn); - mJournalInfos.addColumn (new QuestIndexColumn); - mJournalInfos.addColumn (new QuestDescriptionColumn); + mJournalInfos.addColumn (new StringIdColumn); + mJournalInfos.addColumn (new RecordStateColumn); + mJournalInfos.addColumn (new QuestStatusTypeColumn); + mJournalInfos.addColumn (new QuestIndexColumn); + mJournalInfos.addColumn (new QuestDescriptionColumn); mCells.addColumn (new StringIdColumn); mCells.addColumn (new RecordStateColumn); diff --git a/apps/opencs/model/world/info.hpp b/apps/opencs/model/world/info.hpp new file mode 100644 index 0000000000..1bcb2dc2d0 --- /dev/null +++ b/apps/opencs/model/world/info.hpp @@ -0,0 +1,14 @@ +#ifndef CSM_WOLRD_INFO_H +#define CSM_WOLRD_INFO_H + +#include + +namespace CSMWorld +{ + struct Info : public ESM::DialInfo + { + std::string mTopicId; + }; +} + +#endif diff --git a/apps/opencs/model/world/infocollection.cpp b/apps/opencs/model/world/infocollection.cpp index 42425ecf57..e21c3d4776 100644 --- a/apps/opencs/model/world/infocollection.cpp +++ b/apps/opencs/model/world/infocollection.cpp @@ -2,15 +2,16 @@ #include "infocollection.hpp" #include +#include -void CSMWorld::InfoCollection::load (const ESM::DialInfo& record, bool base) +void CSMWorld::InfoCollection::load (const Info& record, bool base) { int index = searchId (record.mId); if (index==-1) { // new record - Record record2; + Record record2; record2.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; (base ? record2.mBase : record2.mModified) = record; @@ -19,7 +20,7 @@ void CSMWorld::InfoCollection::load (const ESM::DialInfo& record, bool base) else { // old record - Record record2 = getRecord (index); + Record record2 = getRecord (index); if (base) record2.mBase = record; @@ -56,14 +57,15 @@ void CSMWorld::InfoCollection::load (ESM::ESMReader& reader, bool base, const ES } else { - Record record = getRecord (index); + Record record = getRecord (index); record.mState = RecordBase::State_Deleted; setRecord (index, record); } } else { - ESM::DialInfo record; + Info record; + record.mTopicId = dialogue.mId; record.mId = id; record.load (reader); diff --git a/apps/opencs/model/world/infocollection.hpp b/apps/opencs/model/world/infocollection.hpp index 8bb8e53098..5faf61a8c7 100644 --- a/apps/opencs/model/world/infocollection.hpp +++ b/apps/opencs/model/world/infocollection.hpp @@ -1,9 +1,8 @@ #ifndef CSM_WOLRD_INFOCOLLECTION_H #define CSM_WOLRD_INFOCOLLECTION_H -#include - #include "collection.hpp" +#include "info.hpp" namespace ESM { @@ -12,9 +11,9 @@ namespace ESM namespace CSMWorld { - class InfoCollection : public Collection > + class InfoCollection : public Collection > { - void load (const ESM::DialInfo& record, bool base); + void load (const Info& record, bool base); public: From e6960d915a1399229538c761d39cf9f7e418b8ff Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 2 Nov 2013 02:48:30 +0100 Subject: [PATCH 160/434] Add simple Ogre widget --- apps/opencs/CMakeLists.txt | 4 + apps/opencs/editor.cpp | 17 ++++ apps/opencs/main.cpp | 8 ++ apps/opencs/view/render/scenewidget.cpp | 128 ++++++++++++++++++++++++ apps/opencs/view/render/scenewidget.hpp | 40 ++++++++ apps/opencs/view/world/scenesubview.cpp | 15 ++- extern/sdl4ogre/sdlinputwrapper.cpp | 4 +- 7 files changed, 205 insertions(+), 11 deletions(-) create mode 100644 apps/opencs/view/render/scenewidget.cpp create mode 100644 apps/opencs/view/render/scenewidget.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index f918cfebfb..ed6a6b29e0 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -63,6 +63,10 @@ opencs_units (view/world scenetoolmode ) +opencs_units (view/render + scenewidget + ) + opencs_units_noqt (view/world dialoguesubview subviews enumdelegate vartypedelegate recordstatusdelegate idtypedelegate datadisplaydelegate diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 9a6832ec00..31cbc3dbaa 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -9,6 +9,9 @@ #include "model/doc/document.hpp" #include "model/world/data.hpp" +#include +#include + CS::Editor::Editor() : mViewManager (mDocumentManager) { @@ -212,6 +215,20 @@ int CS::Editor::run() if (mLocal.empty()) return 1; + // TODO: setting + Ogre::Root::getSingleton().setRenderSystem(Ogre::Root::getSingleton().getRenderSystemByName("OpenGL Rendering Subsystem")); + + Ogre::Root::getSingleton().initialise(false); + + // Create a hidden background window to keep resources + Ogre::NameValuePairList params; + params.insert(std::make_pair("title", "")); + params.insert(std::make_pair("FSAA", "0")); + params.insert(std::make_pair("vsync", "false")); + params.insert(std::make_pair("hidden", "true")); + Ogre::RenderWindow* hiddenWindow = Ogre::Root::getSingleton().createRenderWindow("InactiveHidden", 1, 1, false, ¶ms); + hiddenWindow->setActive(false); + mStartup.show(); QApplication::setQuitOnLastWindowClosed (true); diff --git a/apps/opencs/main.cpp b/apps/opencs/main.cpp index ef7123c204..9f55f6004e 100644 --- a/apps/opencs/main.cpp +++ b/apps/opencs/main.cpp @@ -7,6 +7,8 @@ #include #include +#include + class Application : public QApplication { private: @@ -33,6 +35,12 @@ class Application : public QApplication int main(int argc, char *argv[]) { Q_INIT_RESOURCE (resources); + + // TODO: Ogre startup shouldn't be here, but it currently has to: + // SceneWidget destructor will delete the created render window, which would be called _after_ Root has shut down :( + OgreInit::OgreInit ogreInit; + ogreInit.init("./opencsOgre.log"); // TODO log path? + Application mApplication (argc, argv); mApplication.setWindowIcon (QIcon (":./opencs.png")); diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp new file mode 100644 index 0000000000..c8b37e9bb0 --- /dev/null +++ b/apps/opencs/view/render/scenewidget.cpp @@ -0,0 +1,128 @@ +#include "scenewidget.hpp" + +#include +#include + +#include +#include +#include + +namespace CSVRender +{ + + SceneWidget::SceneWidget(QWidget *parent) + : QWidget(parent) + , mWindow(NULL) + , mCamera(NULL) + , mSceneMgr(NULL) + { + setAttribute(Qt::WA_PaintOnScreen); + setAttribute(Qt::WA_NoSystemBackground); + + mSceneMgr = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC); + + // Throw in a random color just to make sure multiple scenes work + Ogre::Real r = Ogre::Math::RangeRandom(0, 1); + Ogre::Real g = Ogre::Math::RangeRandom(0, 1); + Ogre::Real b = Ogre::Math::RangeRandom(0, 1); + mSceneMgr->setAmbientLight(Ogre::ColourValue(r,g,b,1)); + + Ogre::Light* l = mSceneMgr->createLight(); + l->setType (Ogre::Light::LT_DIRECTIONAL); + l->setDirection (Ogre::Vector3(-0.4, -0.7, 0.3)); + l->setDiffuseColour (Ogre::ColourValue(0.7,0.7,0.7)); + + mCamera = mSceneMgr->createCamera("foo"); + + Ogre::Entity* ent = mSceneMgr->createEntity("cube", Ogre::SceneManager::PT_CUBE); + ent->setMaterialName("BaseWhite"); + + mSceneMgr->getRootSceneNode()->attachObject(ent); + + mCamera->setPosition(300,300,300); + mCamera->lookAt(0,0,0); + mCamera->setNearClipDistance(0.1); + mCamera->setFarClipDistance(3000); + } + + void SceneWidget::updateOgreWindow() + { + if (mWindow) + { + Ogre::Root::getSingleton().destroyRenderTarget(mWindow); + mWindow = NULL; + } + + std::stringstream windowHandle; + windowHandle << this->winId(); + + std::stringstream windowTitle; + static int count=0; + windowTitle << ++count; + + Ogre::NameValuePairList params; + + params.insert(std::make_pair("externalWindowHandle", windowHandle.str())); + params.insert(std::make_pair("title", windowTitle.str())); + params.insert(std::make_pair("FSAA", "0")); // TODO setting + params.insert(std::make_pair("vsync", "false")); // TODO setting + + mWindow = Ogre::Root::getSingleton().createRenderWindow(windowTitle.str(), this->width(), this->height(), false, ¶ms); + mWindow->addViewport(mCamera)->setBackgroundColour(Ogre::ColourValue(0.3,0.3,0.3,1)); + + Ogre::Real aspectRatio = Ogre::Real(width()) / Ogre::Real(height()); + mCamera->setAspectRatio(aspectRatio); + } + + SceneWidget::~SceneWidget() + { + Ogre::Root::getSingleton().destroyRenderTarget(mWindow); + } + + void SceneWidget::paintEvent(QPaintEvent* e) + { + if (!mWindow) + updateOgreWindow(); + + mWindow->update(); + e->accept(); + } + + + QPaintEngine* SceneWidget::paintEngine() const + { + // We don't want another paint engine to get in the way. + // So we return nothing. + return NULL; + } + + void SceneWidget::resizeEvent(QResizeEvent *e) + { + if (!mWindow) + return; + + const QSize &newSize = e->size(); + + // TODO: Fix Ogre to handle this more consistently (fixed in 1.9) +#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX + mWindow->resize(newSize.width(), newSize.height()); +#else + mWindow->windowMovedOrResized(); +#endif + + Ogre::Real aspectRatio = Ogre::Real(newSize.width()) / Ogre::Real(newSize.height()); + mCamera->setAspectRatio(aspectRatio); + } + + bool SceneWidget::event(QEvent *e) + { + if (e->type() == QEvent::WinIdChange) + { + // I haven't actually seen this happen yet. + if (mWindow) + updateOgreWindow(); + } + return QWidget::event(e); + } + +} diff --git a/apps/opencs/view/render/scenewidget.hpp b/apps/opencs/view/render/scenewidget.hpp new file mode 100644 index 0000000000..355a6331e9 --- /dev/null +++ b/apps/opencs/view/render/scenewidget.hpp @@ -0,0 +1,40 @@ +#ifndef OPENCS_VIEW_SCENEWIDGET_H +#define OPENCS_VIEW_SCENEWIDGET_H + +#include + +namespace Ogre +{ + class Camera; + class SceneManager; + class RenderWindow; +} + +namespace CSVRender +{ + + class SceneWidget : public QWidget + { + Q_OBJECT + + public: + SceneWidget(QWidget *parent); + virtual ~SceneWidget(void); + + QPaintEngine* paintEngine() const; + + private: + void paintEvent(QPaintEvent* e); + void resizeEvent(QResizeEvent* e); + bool event(QEvent* e); + + void updateOgreWindow(); + + Ogre::Camera* mCamera; + Ogre::SceneManager* mSceneMgr; + Ogre::RenderWindow* mWindow; + }; + +} + +#endif diff --git a/apps/opencs/view/world/scenesubview.cpp b/apps/opencs/view/world/scenesubview.cpp index e3618c5493..83b30be133 100644 --- a/apps/opencs/view/world/scenesubview.cpp +++ b/apps/opencs/view/world/scenesubview.cpp @@ -9,6 +9,8 @@ #include "../filter/filterbox.hpp" +#include "../render/scenewidget.hpp" + #include "tablebottombox.hpp" #include "creator.hpp" #include "scenetoolbar.hpp" @@ -41,15 +43,10 @@ toolbar->addTool (new SceneToolMode (toolbar)); toolbar->addTool (new SceneToolMode (toolbar)); layout2->addWidget (toolbar, 0); - /// \todo replace with rendering widget - QPalette palette2 (palette()); - palette2.setColor (QPalette::Background, Qt::white); - QLabel *placeholder = new QLabel ("Here goes the 3D scene", this); - placeholder->setAutoFillBackground (true); - placeholder->setPalette (palette2); - placeholder->setAlignment (Qt::AlignHCenter); - layout2->addWidget (placeholder, 1); + CSVRender::SceneWidget* sceneWidget = new CSVRender::SceneWidget(this); + + layout2->addWidget (sceneWidget, 1); layout->insertLayout (0, layout2, 1); @@ -79,4 +76,4 @@ void CSVWorld::SceneSubView::updateEditorSetting(const QString &settingName, con void CSVWorld::SceneSubView::setStatusBar (bool show) { mBottom->setStatusBar (show); -} \ No newline at end of file +} diff --git a/extern/sdl4ogre/sdlinputwrapper.cpp b/extern/sdl4ogre/sdlinputwrapper.cpp index df74bba3b6..e71ed909e5 100644 --- a/extern/sdl4ogre/sdlinputwrapper.cpp +++ b/extern/sdl4ogre/sdlinputwrapper.cpp @@ -126,7 +126,7 @@ namespace SFO case SDL_WINDOWEVENT_SIZE_CHANGED: int w,h; SDL_GetWindowSize(mSDLWindow, &w, &h); - // TODO: Fix Ogre to handle this more consistently + // TODO: Fix Ogre to handle this more consistently (fixed in 1.9) #if OGRE_PLATFORM == OGRE_PLATFORM_LINUX mOgreWindow->resize(w, h); #else @@ -137,7 +137,7 @@ namespace SFO break; case SDL_WINDOWEVENT_RESIZED: - // TODO: Fix Ogre to handle this more consistently + // TODO: Fix Ogre to handle this more consistently (fixed in 1.9) #if OGRE_PLATFORM == OGRE_PLATFORM_LINUX mOgreWindow->resize(evt.window.data1, evt.window.data2); #else From cddece4f9e84de70e7861c24373c62a37d4356ad Mon Sep 17 00:00:00 2001 From: graffy76 Date: Fri, 1 Nov 2013 21:47:26 -0500 Subject: [PATCH 161/434] Another stab at fixing the pathing problem... --- apps/opencs/view/doc/adjusterwidget.cpp | 6 ++++- apps/opencs/view/doc/filedialog.cpp | 9 ++++--- .../contentselector/model/contentmodel.cpp | 27 ++++++++++++------- components/contentselector/model/esmfile.cpp | 6 ++--- components/contentselector/model/esmfile.hpp | 6 ++--- .../contentselector/view/contentselector.cpp | 5 ++-- 6 files changed, 37 insertions(+), 22 deletions(-) diff --git a/apps/opencs/view/doc/adjusterwidget.cpp b/apps/opencs/view/doc/adjusterwidget.cpp index 09e58690fc..5ebfacd035 100644 --- a/apps/opencs/view/doc/adjusterwidget.cpp +++ b/apps/opencs/view/doc/adjusterwidget.cpp @@ -10,6 +10,9 @@ #include #include +#include + + CSVDoc::AdjusterWidget::AdjusterWidget (QWidget *parent) : QWidget (parent), mValid (false), mAction (ContentAction_Undefined) { @@ -76,7 +79,8 @@ void CSVDoc::AdjusterWidget::setName (const QString& name, bool addon) path.extension() == ".esp"); bool isFilePathChanged = (path.parent_path().string() != mLocalData.string()); - + qDebug() << "current path: " << path.parent_path().c_str(); + qDebug() << "data-local: " << mLocalData.c_str(); if (isLegacyPath) path.replace_extension (addon ? ".omwaddon" : ".omwgame"); diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index c0cda26dc9..ce7d7dfb0b 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -17,6 +17,8 @@ #include "filewidget.hpp" #include "adjusterwidget.hpp" +#include + CSVDoc::FileDialog::FileDialog(QWidget *parent) : QDialog(parent), mSelector (0), mFileWidget (0), mAdjusterWidget (0) { @@ -38,7 +40,7 @@ QStringList CSVDoc::FileDialog::selectedFilePaths() QStringList filePaths; foreach (ContentSelectorModel::EsmFile *file, mSelector->selectedFiles() ) - filePaths.append(file->path()); + filePaths.append(file->filePath()); return filePaths; } @@ -139,7 +141,8 @@ void CSVDoc::FileDialog::slotUpdateAcceptButton(const QString &name, bool) else { ContentSelectorModel::EsmFile *file = mSelector->selectedFiles().back();; - mAdjusterWidget->setName (file->fileName(), !file->isGameFile()); + mAdjusterWidget->setName (file->filePath(), !file->isGameFile()); + qDebug() << "setting filepath " << file->filePath(); } ui.projectButtonBox->button (QDialogButtonBox::Ok)->setEnabled (success); @@ -168,7 +171,7 @@ void CSVDoc::FileDialog::slotOpenFile() { ContentSelectorModel::EsmFile *file = mSelector->selectedFiles().back(); - mAdjusterWidget->setName (file->fileName(), !file->isGameFile()); + mAdjusterWidget->setName (file->filePath(), !file->isGameFile()); emit signalOpenFiles (mAdjusterWidget->getPath()); } diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 0674642eed..ea389c2bf6 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -69,7 +69,7 @@ const ContentSelectorModel::EsmFile *ContentSelectorModel::ContentModel::item(co { foreach (const EsmFile *file, mFiles) { - if (name == file->fileName()) + if (name == file->filePath()) return file; } return 0; @@ -158,7 +158,7 @@ QVariant ContentSelectorModel::ContentModel::data(const QModelIndex &index, int case Qt::CheckStateRole: { if (!file->isGameFile()) - return isChecked(file->fileName()); + return isChecked(file->filePath()); break; } @@ -174,7 +174,7 @@ QVariant ContentSelectorModel::ContentModel::data(const QModelIndex &index, int } case Qt::UserRole + 1: - return isChecked(file->fileName()); + return isChecked(file->filePath()); break; } return QVariant(); @@ -186,7 +186,7 @@ bool ContentSelectorModel::ContentModel::setData(const QModelIndex &index, const return false; EsmFile *file = item(index.row()); - QString fileName = file->fileName(); + QString fileName = file->filePath(); bool success = false; switch(role) @@ -396,6 +396,7 @@ bool ContentSelectorModel::ContentModel::canBeChecked(const EsmFile *file) const void ContentSelectorModel::ContentModel::addFile(EsmFile *file) { + qDebug() << "adding file: " << file->filePath(); beginInsertRows(QModelIndex(), mFiles.count(), mFiles.count()); mFiles.append(file); endInsertRows(); @@ -417,6 +418,8 @@ void ContentSelectorModel::ContentModel::addFiles(const QString &path) // Create a decoder for non-latin characters in esx metadata QTextDecoder *decoder = codec->makeDecoder(); + qDebug() << "searching path: " << path << " files found: " << dir.entryList().size(); + foreach (const QString &path, dir.entryList()) { QFileInfo info(dir.absoluteFilePath(path)); @@ -430,18 +433,24 @@ void ContentSelectorModel::ContentModel::addFiles(const QString &path) fileReader.open(dir.absoluteFilePath(path).toStdString()); foreach (const ESM::Header::MasterData &item, fileReader.getGameFiles()) + { + qDebug() << "adding gamefile: " << item.name.c_str(); file->addGameFile(QString::fromStdString(item.name)); + } file->setAuthor (decoder->toUnicode(fileReader.getAuthor().c_str())); file->setDate (info.lastModified()); file->setFormat (fileReader.getFormat()); - file->setPath (info.absoluteFilePath()); + file->setFilePath (info.absoluteFilePath()); file->setDescription(decoder->toUnicode(fileReader.getDesc().c_str())); // Put the file in the table - if (item(path) == 0) + if (item(file->filePath()) == 0) + { + qDebug () << "adding file " << file->filePath(); addFile(file); + } } catch(std::runtime_error &e) { // An error occurred while reading the .esp @@ -543,8 +552,8 @@ void ContentSelectorModel::ContentModel::setCheckState(const QString &name, bool { if (downstreamFile->gameFiles().contains(name)) { - if (mCheckStates.contains(downstreamFile->fileName())) - mCheckStates[downstreamFile->fileName()] = Qt::Unchecked; + if (mCheckStates.contains(downstreamFile->filePath())) + mCheckStates[downstreamFile->filePath()] = Qt::Unchecked; emit dataChanged(indexFromItem(downstreamFile), indexFromItem(downstreamFile)); } @@ -558,7 +567,7 @@ ContentSelectorModel::ContentFileList ContentSelectorModel::ContentModel::checke foreach (EsmFile *file, mFiles) { - if (isChecked(file->fileName())) + if (isChecked(file->filePath())) list << file; } diff --git a/components/contentselector/model/esmfile.cpp b/components/contentselector/model/esmfile.cpp index 9dfe49ebaf..35f78386ce 100644 --- a/components/contentselector/model/esmfile.cpp +++ b/components/contentselector/model/esmfile.cpp @@ -34,7 +34,7 @@ void ContentSelectorModel::EsmFile::setFormat(int format) mFormat = format; } -void ContentSelectorModel::EsmFile::setPath(const QString &path) +void ContentSelectorModel::EsmFile::setFilePath(const QString &path) { mPath = path; } @@ -81,7 +81,7 @@ QVariant ContentSelectorModel::EsmFile::fileProperty(const FileProperty prop) co return mModified.toString(Qt::ISODate); break; - case FileProperty_Path: + case FileProperty_FilePath: return mPath; break; @@ -118,7 +118,7 @@ void ContentSelectorModel::EsmFile::setFileProperty (const FileProperty prop, co mModified = QDateTime::fromString(value); break; - case FileProperty_Path: + case FileProperty_FilePath: mPath = value; break; diff --git a/components/contentselector/model/esmfile.hpp b/components/contentselector/model/esmfile.hpp index 743b1c5a68..fc0cca8a2b 100644 --- a/components/contentselector/model/esmfile.hpp +++ b/components/contentselector/model/esmfile.hpp @@ -23,7 +23,7 @@ namespace ContentSelectorModel FileProperty_Author = 1, FileProperty_Format = 2, FileProperty_DateModified = 3, - FileProperty_Path = 4, + FileProperty_FilePath = 4, FileProperty_Description = 5, FileProperty_GameFile = 6 }; @@ -41,7 +41,7 @@ namespace ContentSelectorModel void setSize(const int size); void setDate(const QDateTime &modified); void setFormat(const int format); - void setPath(const QString &path); + void setFilePath(const QString &path); void setGameFiles(const QStringList &gameFiles); void setDescription(const QString &description); @@ -52,7 +52,7 @@ namespace ContentSelectorModel inline QString author() const { return mAuthor; } inline QDateTime modified() const { return mModified; } inline float format() const { return mFormat; } - inline QString path() const { return mPath; } + inline QString filePath() const { return mPath; } inline const QStringList &gameFiles() const { return mGameFiles; } inline QString description() const { return mDescription; } inline QString toolTip() const { return sToolTip.arg(mAuthor) diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index b9e5189312..d12c8cb242 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -42,9 +42,6 @@ void ContentSelectorView::ContentSelector::buildGameFileView() connect (ui.gameFileView, SIGNAL (currentIndexChanged(int)), this, SLOT (slotCurrentGameFileIndexChanged(int))); - connect (ui.gameFileView, SIGNAL (currentIndexChanged (int)), - this, SIGNAL (signalCurrentGamefileIndexChanged (int))); - ui.gameFileView->setCurrentIndex(-1); ui.gameFileView->setCurrentIndex(0); } @@ -145,6 +142,8 @@ void ContentSelectorView::ContentSelector::slotCurrentGameFileIndexChanged(int i if (proxy) proxy->setDynamicSortFilter(true); + + emit signalCurrentGamefileIndexChanged (index); } void ContentSelectorView::ContentSelector::slotAddonTableItemClicked(const QModelIndex &index) From afce10cf37d261cf749faeed509e5c2fd63f5131 Mon Sep 17 00:00:00 2001 From: Lukasz Gromanowski Date: Sat, 2 Nov 2013 16:20:40 +0100 Subject: [PATCH 162/434] Fixes #597: Assertion `dialogue->mId == id' failed in esmstore.cpp It seems that assertion was unnecessary, after removing it, dialogs related to moon-and-star in "Path of the Incarnate" quest were correctly loaded (dumped DialInfo records were correct). Signed-off-by: Lukasz Gromanowski --- apps/openmw/mwworld/esmstore.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 7703f2d233..5cee7efd97 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -100,11 +100,7 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) it->second->load(esm, id); if (n.val==ESM::REC_DIAL) { - // dirty hack, but it is better than non-const search() - // or friends - //dialogue = &mDialogs.mStatic.back(); dialogue = const_cast(mDialogs.find(id)); - assert (dialogue->mId == id); } else { dialogue = 0; } From 973803eb2f6cdbdaded0649250092e7adcc526bc Mon Sep 17 00:00:00 2001 From: graffy76 Date: Sun, 3 Nov 2013 00:02:46 -0500 Subject: [PATCH 163/434] Fixed pathing issues in launcher --- apps/launcher/datafilespage.cpp | 44 +++++++++++-- apps/launcher/datafilespage.hpp | 58 ++++++++++++++++ apps/launcher/settings/gamesettings.cpp | 2 +- apps/launcher/settings/gamesettings.hpp | 5 -- apps/opencs/view/doc/adjusterwidget.cpp | 6 +- apps/opencs/view/doc/filedialog.cpp | 3 - .../contentselector/model/contentmodel.cpp | 66 +++++++++---------- .../contentselector/model/contentmodel.hpp | 3 +- components/contentselector/model/esmfile.cpp | 5 +- components/contentselector/model/esmfile.hpp | 1 + .../contentselector/view/contentselector.cpp | 12 ++-- 11 files changed, 143 insertions(+), 62 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index e246b45154..71d072599a 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #include @@ -37,6 +36,10 @@ Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, GameSet void Launcher::DataFilesPage::loadSettings() { + QStringList paths = mGameSettings.getDataDirs(); + paths.insert (0, mDataLocal); + PathIterator pathIterator (paths); + QString profileName = ui.profilesComboBox->currentText(); QStringList files = mLauncherSettings.values(QString("Profiles/") + profileName + QString("/game"), Qt::MatchExactly); @@ -47,10 +50,37 @@ void Launcher::DataFilesPage::loadSettings() QString gameFile (""); if (files.size()>0) - gameFile = files.at (0); + { + gameFile = pathIterator.findFirstPath (files.at(0)); - mSelector->setGameFile(gameFile); - mSelector->setCheckStates(addons); + if (!gameFile.isEmpty()) + mSelector->setGameFile (gameFile); +/* else + { + //throw gamefile error here. + }*/ + } + + QStringList missingFiles; + QStringList foundFiles; + + foreach (const QString &addon, addons) + { + QString filePath = pathIterator.findFirstPath (addon); + + if (filePath.isEmpty()) + missingFiles << addon; + else + foundFiles << filePath; + } +/* + if (missingFiles.size() > 0) + { + //throw addons error here. + } +*/ + if (foundFiles.size() > 0) + mSelector->setCheckStates (foundFiles); } void Launcher::DataFilesPage::saveSettings(const QString &profile) @@ -191,10 +221,10 @@ void Launcher::DataFilesPage::setupDataFiles() foreach (const QString &path, paths) mSelector->addFiles(path); - QString dataLocal = mGameSettings.getDataLocal(); + mDataLocal = mGameSettings.getDataLocal(); - if (!dataLocal.isEmpty()) - mSelector->addFiles(dataLocal); + if (!mDataLocal.isEmpty()) + mSelector->addFiles(mDataLocal); QStringList profiles = mLauncherSettings.subKeys(QString("Profiles/")); QString profile = mLauncherSettings.value(QString("Profiles/currentprofile")); diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index e394e6f41f..37603a2106 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -4,6 +4,10 @@ #include "ui_datafilespage.h" #include + +#include +#include + class QSortFilterProxyModel; class QAbstractItemModel; class QMenu; @@ -61,6 +65,8 @@ namespace Launcher GameSettings &mGameSettings; LauncherSettings &mLauncherSettings; + QString mDataLocal; + void setPluginsCheckstates(Qt::CheckState state); void buildView(); @@ -73,6 +79,58 @@ namespace Launcher bool showDeleteMessageBox (const QString &text); void addProfile (const QString &profile, bool setAsCurrent); void checkForDefaultProfile(); + + class PathIterator + { + QStringList::ConstIterator mCitEnd; + QStringList::ConstIterator mCitCurrent; + QStringList::ConstIterator mCitBegin; + QString mFile; + QString mFilePath; + + public: + PathIterator (const QStringList &list) + { + mCitBegin = list.constBegin(); + mCitCurrent = mCitBegin; + mCitEnd = list.constEnd(); + } + + QString findFirstPath (const QString &file) + { + mCitCurrent = mCitBegin; + mFile = file; + return path(); + } + + QString findNextPath () { return path(); } + + private: + + QString path () + { + bool success = false; + QDir dir; + QFileInfo file; + + while (!success) + { + if (mCitCurrent == mCitEnd) + break; + + dir.setPath (*(mCitCurrent++)); + file.setFile (dir.absoluteFilePath (mFile)); + + success = file.exists(); + } + + if (success) + return file.absoluteFilePath(); + + return ""; + } + + }; }; } #endif diff --git a/apps/launcher/settings/gamesettings.cpp b/apps/launcher/settings/gamesettings.cpp index 32eb2071f7..83c0995291 100644 --- a/apps/launcher/settings/gamesettings.cpp +++ b/apps/launcher/settings/gamesettings.cpp @@ -169,7 +169,7 @@ bool Launcher::GameSettings::writeFile(QTextStream &stream) return true; } -bool GameSettings::hasMaster() +bool Launcher::GameSettings::hasMaster() { bool result = false; QStringList content = mSettings.values(QString("content")); diff --git a/apps/launcher/settings/gamesettings.hpp b/apps/launcher/settings/gamesettings.hpp index 56917a79fd..60236200a9 100644 --- a/apps/launcher/settings/gamesettings.hpp +++ b/apps/launcher/settings/gamesettings.hpp @@ -51,11 +51,6 @@ namespace Launcher bool hasMaster(); - inline QStringList getDataDirs() { return mDataDirs; } - inline void addDataDir(const QString &dir) { if(!dir.isEmpty()) mDataDirs.append(dir); } - inline QString getDataLocal() {return mDataLocal; } - inline bool hasMaster() { return mSettings.count(QString("master")) > 0; } - QStringList values(const QString &key, const QStringList &defaultValues = QStringList()); bool readFile(QTextStream &stream); bool writeFile(QTextStream &stream); diff --git a/apps/opencs/view/doc/adjusterwidget.cpp b/apps/opencs/view/doc/adjusterwidget.cpp index 5ebfacd035..09e58690fc 100644 --- a/apps/opencs/view/doc/adjusterwidget.cpp +++ b/apps/opencs/view/doc/adjusterwidget.cpp @@ -10,9 +10,6 @@ #include #include -#include - - CSVDoc::AdjusterWidget::AdjusterWidget (QWidget *parent) : QWidget (parent), mValid (false), mAction (ContentAction_Undefined) { @@ -79,8 +76,7 @@ void CSVDoc::AdjusterWidget::setName (const QString& name, bool addon) path.extension() == ".esp"); bool isFilePathChanged = (path.parent_path().string() != mLocalData.string()); - qDebug() << "current path: " << path.parent_path().c_str(); - qDebug() << "data-local: " << mLocalData.c_str(); + if (isLegacyPath) path.replace_extension (addon ? ".omwaddon" : ".omwgame"); diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index ce7d7dfb0b..80b3066ddd 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -17,8 +17,6 @@ #include "filewidget.hpp" #include "adjusterwidget.hpp" -#include - CSVDoc::FileDialog::FileDialog(QWidget *parent) : QDialog(parent), mSelector (0), mFileWidget (0), mAdjusterWidget (0) { @@ -142,7 +140,6 @@ void CSVDoc::FileDialog::slotUpdateAcceptButton(const QString &name, bool) { ContentSelectorModel::EsmFile *file = mSelector->selectedFiles().back();; mAdjusterWidget->setName (file->filePath(), !file->isGameFile()); - qDebug() << "setting filepath " << file->filePath(); } ui.projectButtonBox->button (QDialogButtonBox::Ok)->setEnabled (success); diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 065b9c4974..7661f96083 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -3,9 +3,11 @@ #include #include -#include +#include #include +#include "components/esm/esmreader.hpp" + ContentSelectorModel::ContentModel::ContentModel(QObject *parent) : QAbstractTableModel(parent), mMimeType ("application/omwcontent"), @@ -380,15 +382,18 @@ bool ContentSelectorModel::ContentModel::canBeChecked(const EsmFile *file) const //addon can be checked if its gamefile is foreach (const QString &fileName, file->gameFiles()) { - const EsmFile *dependency = item(fileName); - - if (!dependency) - continue; - - if (dependency->isGameFile()) + foreach (EsmFile *dependency, mFiles) { - if (isChecked(fileName)) - return true; + //compare filenames only. Multiple instances + //of the filename (with different paths) is not relevant here. + if (!(dependency->fileName() == fileName)) + continue; + + if (dependency->isGameFile()) + { + if (isChecked(dependency->filePath())) + return true; + } } } return false; @@ -396,7 +401,6 @@ bool ContentSelectorModel::ContentModel::canBeChecked(const EsmFile *file) const void ContentSelectorModel::ContentModel::addFile(EsmFile *file) { - qDebug() << "adding file: " << file->filePath(); beginInsertRows(QModelIndex(), mFiles.count(), mFiles.count()); mFiles.append(file); endInsertRows(); @@ -418,8 +422,6 @@ void ContentSelectorModel::ContentModel::addFiles(const QString &path) // Create a decoder for non-latin characters in esx metadata QTextDecoder *decoder = codec->makeDecoder(); - qDebug() << "searching path: " << path << " files found: " << dir.entryList().size(); - foreach (const QString &path, dir.entryList()) { QFileInfo info(dir.absoluteFilePath(path)); @@ -433,10 +435,7 @@ void ContentSelectorModel::ContentModel::addFiles(const QString &path) fileReader.open(dir.absoluteFilePath(path).toStdString()); foreach (const ESM::Header::MasterData &item, fileReader.getGameFiles()) - { - qDebug() << "adding gamefile: " << item.name.c_str(); file->addGameFile(QString::fromStdString(item.name)); - } file->setAuthor (decoder->toUnicode(fileReader.getAuthor().c_str())); file->setDate (info.lastModified()); @@ -447,10 +446,7 @@ void ContentSelectorModel::ContentModel::addFiles(const QString &path) // Put the file in the table if (item(file->filePath()) == 0) - { - qDebug () << "adding file " << file->filePath(); addFile(file); - } } catch(std::runtime_error &e) { // An error occurred while reading the .esp @@ -510,10 +506,23 @@ bool ContentSelectorModel::ContentModel::isChecked(const QString& name) const return false; } -void ContentSelectorModel::ContentModel::setCheckState(const QString &name, bool checkState) +void ContentSelectorModel::ContentModel::setCheckStates (const QStringList &fileList, bool isChecked) +{ + foreach (const QString &file, fileList) + { + setCheckState (file, isChecked); + } +} + +bool ContentSelectorModel::ContentModel::setCheckState(const QString &name, bool checkState) { if (name.isEmpty()) - return; + return false; + + const EsmFile *file = item(name); + + if (!file) + return false; Qt::CheckState state = Qt::Unchecked; @@ -523,8 +532,6 @@ void ContentSelectorModel::ContentModel::setCheckState(const QString &name, bool mCheckStates[name] = state; emit dataChanged(indexFromItem(item(name)), indexFromItem(item(name))); - const EsmFile *file = item(name); - if (file->isGameFile()) emit dataChanged (index(0,0), index(rowCount()-1,0)); @@ -559,29 +566,20 @@ void ContentSelectorModel::ContentModel::setCheckState(const QString &name, bool } } } + + return true; } ContentSelectorModel::ContentFileList ContentSelectorModel::ContentModel::checkedItems() const { ContentFileList list; + // TODO: // First search for game files and next addons, // so we get more or less correct game files vs addons order. foreach (EsmFile *file, mFiles) - { -<<<<<<< HEAD if (isChecked(file->filePath())) -======= - if (isChecked(file->fileName()) && file->isGameFile()) list << file; - } - - foreach (EsmFile *file, mFiles) - { - if (isChecked(file->fileName()) && !file->isGameFile()) ->>>>>>> f5fbe7361fad698e8dd3330e9820a157800be8ae - list << file; - } return list; } diff --git a/components/contentselector/model/contentmodel.hpp b/components/contentselector/model/contentmodel.hpp index 0d6c52cc63..ae49dd27da 100644 --- a/components/contentselector/model/contentmodel.hpp +++ b/components/contentselector/model/contentmodel.hpp @@ -45,7 +45,8 @@ namespace ContentSelectorModel const EsmFile *item(const QString &name) const; bool isChecked(const QString &name) const; - void setCheckState(const QString &name, bool isChecked); + bool setCheckState(const QString &name, bool isChecked); + void setCheckStates (const QStringList &fileList, bool isChecked); ContentFileList checkedItems() const; void uncheckAll(); diff --git a/components/contentselector/model/esmfile.cpp b/components/contentselector/model/esmfile.cpp index 35f78386ce..a0a09105a7 100644 --- a/components/contentselector/model/esmfile.cpp +++ b/components/contentselector/model/esmfile.cpp @@ -6,8 +6,9 @@ int ContentSelectorModel::EsmFile::sPropertyCount = 7; QString ContentSelectorModel::EsmFile::sToolTip = QString("Author: %1
\ Version: %2
\ -
Description:
%3
\ -
Dependencies: %4
"); + Path:
%3
\ +
Description:
%4
\ +
Dependencies: %5
"); ContentSelectorModel::EsmFile::EsmFile(QString fileName, ModelItem *parent) diff --git a/components/contentselector/model/esmfile.hpp b/components/contentselector/model/esmfile.hpp index fc0cca8a2b..ca24b52d11 100644 --- a/components/contentselector/model/esmfile.hpp +++ b/components/contentselector/model/esmfile.hpp @@ -57,6 +57,7 @@ namespace ContentSelectorModel inline QString description() const { return mDescription; } inline QString toolTip() const { return sToolTip.arg(mAuthor) .arg(mFormat) + .arg(mPath) .arg(mDescription) .arg(mGameFiles.join(", ")); } diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index d12c8cb242..4758dd5a05 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -67,10 +67,15 @@ void ContentSelectorView::ContentSelector::setGameFile(const QString &filename) if (!filename.isEmpty()) { - index = ui.gameFileView->findText(filename); + const ContentSelectorModel::EsmFile *file = mContentModel->item (filename); + index = ui.gameFileView->findText (file->fileName()); //verify that the current index is also checked in the model - mContentModel->setCheckState(filename, true); + if (!mContentModel->setCheckState(filename, true)) + { + //throw error in case file not found? + return; + } } ui.gameFileView->setCurrentIndex(index); @@ -86,8 +91,7 @@ void ContentSelectorView::ContentSelector::setCheckStates(const QStringList &lis if (list.isEmpty()) return; - foreach (const QString &file, list) - mContentModel->setCheckState(file, Qt::Checked); + mContentModel->setCheckStates (list, true); } ContentSelectorModel::ContentFileList From 4724df7e9bb0c0dfbdca1b86022651ec8adf60bf Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 3 Nov 2013 10:48:50 +0100 Subject: [PATCH 164/434] added topic/journal column to info tables --- apps/opencs/model/world/columnimp.hpp | 30 +++++++++++++++++++++++++++ apps/opencs/model/world/columns.cpp | 2 ++ apps/opencs/model/world/columns.hpp | 2 ++ apps/opencs/model/world/data.cpp | 2 ++ 4 files changed, 36 insertions(+) diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index dc80f6eeb4..5d9fe9a1b1 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -1395,6 +1395,36 @@ namespace CSMWorld return true; } }; + + template + struct TopicColumn : public Column + { + TopicColumn (bool journal) : Column (journal ? Columns::ColumnId_Journal : Columns::ColumnId_Topic, ColumnBase::Display_String) {} + + virtual QVariant get (const Record& record) const + { + return QString::fromUtf8 (record.get().mTopicId.c_str()); + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + + record2.mTopicId = data.toString().toUtf8().constData(); + + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + + virtual bool isUserEditable() const + { + return false; + } + }; } #endif diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index 307862b1bb..286afc64d4 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -163,6 +163,8 @@ namespace CSMWorld { ColumnId_QuestIndex, "Quest Index" }, { ColumnId_QuestStatusType, "Quest Status" }, { ColumnId_QuestDescription, "Quest Description" }, + { ColumnId_Topic, "Topic" }, + { ColumnId_Journal, "Journal", }, { ColumnId_UseValue1, "Use value 1" }, { ColumnId_UseValue2, "Use value 2" }, diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index d7b96cab6b..6965d4c313 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -156,6 +156,8 @@ namespace CSMWorld ColumnId_QuestIndex = 143, ColumnId_QuestStatusType = 144, ColumnId_QuestDescription = 145, + ColumnId_Topic = 146, + ColumnId_Journal = 147, // Allocated to a separate value range, so we don't get a collision should we ever need // to extend the number of use values. diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index bfec13f909..88fb9fdedb 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -151,9 +151,11 @@ CSMWorld::Data::Data() : mRefs (mCells) mTopicInfos.addColumn (new StringIdColumn); mTopicInfos.addColumn (new RecordStateColumn); + mTopicInfos.addColumn (new TopicColumn (false)); mJournalInfos.addColumn (new StringIdColumn); mJournalInfos.addColumn (new RecordStateColumn); + mJournalInfos.addColumn (new TopicColumn (true)); mJournalInfos.addColumn (new QuestStatusTypeColumn); mJournalInfos.addColumn (new QuestIndexColumn); mJournalInfos.addColumn (new QuestDescriptionColumn); From 12c06a56152bd0452c0239b07acf8610a0f6a6b8 Mon Sep 17 00:00:00 2001 From: graffy76 Date: Sun, 3 Nov 2013 06:21:28 -0600 Subject: [PATCH 165/434] Fixed broken dependency check --- apps/opencs/view/doc/filedialog.cpp | 2 +- components/contentselector/model/contentmodel.cpp | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index 80b3066ddd..ab56415a14 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -138,7 +138,7 @@ void CSVDoc::FileDialog::slotUpdateAcceptButton(const QString &name, bool) success = success && !(name.isEmpty()); else { - ContentSelectorModel::EsmFile *file = mSelector->selectedFiles().back();; + ContentSelectorModel::EsmFile *file = mSelector->selectedFiles().back(); mAdjusterWidget->setName (file->filePath(), !file->isGameFile()); } diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 7661f96083..0fb1b42161 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -3,7 +3,6 @@ #include #include -#include #include #include "components/esm/esmreader.hpp" @@ -69,9 +68,14 @@ ContentSelectorModel::EsmFile *ContentSelectorModel::ContentModel::item(int row) } const ContentSelectorModel::EsmFile *ContentSelectorModel::ContentModel::item(const QString &name) const { + EsmFile::FileProperty fp = EsmFile::FileProperty_FileName; + + if (name.contains ('/')) + fp = EsmFile::FileProperty_FilePath; + foreach (const EsmFile *file, mFiles) { - if (name == file->filePath()) + if (name == file->fileProperty (fp).toString()) return file; } return 0; @@ -538,15 +542,15 @@ bool ContentSelectorModel::ContentModel::setCheckState(const QString &name, bool //if we're checking an item, ensure all "upstream" files (dependencies) are checked as well. if (state == Qt::Checked) { - foreach (const QString &upstreamName, file->gameFiles()) + foreach (QString upstreamName, file->gameFiles()) { const EsmFile *upstreamFile = item(upstreamName); if (!upstreamFile) continue; - if (!isChecked(upstreamName)) - mCheckStates[upstreamName] = Qt::Checked; + if (!isChecked(upstreamFile->filePath())) + mCheckStates[upstreamFile->filePath()] = Qt::Checked; emit dataChanged(indexFromItem(upstreamFile), indexFromItem(upstreamFile)); From 1d4b5a2425c8a627b6489db5af7c2b63798542e8 Mon Sep 17 00:00:00 2001 From: graffy76 Date: Sun, 3 Nov 2013 14:02:41 -0600 Subject: [PATCH 166/434] Fix broken launcher content file display / selection scheme Disable selection of content files with missing dependencies (grayed out) --- apps/launcher/datafilespage.cpp | 11 +- apps/launcher/utils/profilescombobox.hpp | 7 ++ .../contentselector/model/contentmodel.cpp | 104 +++++++++++------- .../contentselector/model/contentmodel.hpp | 5 +- .../contentselector/view/contentselector.cpp | 20 +++- 5 files changed, 93 insertions(+), 54 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 71d072599a..5ae850566f 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -79,8 +79,7 @@ void Launcher::DataFilesPage::loadSettings() //throw addons error here. } */ - if (foundFiles.size() > 0) - mSelector->setCheckStates (foundFiles); + mSelector->setCheckStates (foundFiles); } void Launcher::DataFilesPage::saveSettings(const QString &profile) @@ -177,7 +176,7 @@ void Launcher::DataFilesPage::setProfile (const QString &previous, const QString if (!previous.isEmpty() && savePrevious) saveSettings (previous); - ui.profilesComboBox->setCurrentIndex (ui.profilesComboBox->findText (current)); + ui.profilesComboBox->setCurrentProfile (ui.profilesComboBox->findText (current)); loadSettings(); @@ -232,7 +231,7 @@ void Launcher::DataFilesPage::setupDataFiles() foreach (const QString &item, profiles) addProfile (item, false); - addProfile (profile, true); + setProfile (ui.profilesComboBox->findText(profile), false); loadSettings(); } @@ -289,6 +288,10 @@ void Launcher::DataFilesPage::on_deleteProfileAction_triggered() // Remove the profile from the combobox ui.profilesComboBox->removeItem (ui.profilesComboBox->findText (profile)); + removeProfile(profile); + + saveSettings(); + loadSettings(); checkForDefaultProfile(); diff --git a/apps/launcher/utils/profilescombobox.hpp b/apps/launcher/utils/profilescombobox.hpp index 1e27f66a9c..7b83c41b2f 100644 --- a/apps/launcher/utils/profilescombobox.hpp +++ b/apps/launcher/utils/profilescombobox.hpp @@ -4,6 +4,8 @@ #include "components/contentselector/view/combobox.hpp" #include "lineedit.hpp" +#include + class QString; class ProfilesComboBox : public ContentSelectorView::ComboBox @@ -21,6 +23,11 @@ public: explicit ProfilesComboBox(QWidget *parent = 0); void setEditEnabled(bool editable); + void setCurrentProfile(int index) + { + ComboBox::setCurrentIndex(index); + mOldProfile = currentText(); + } signals: void signalProfileTextChanged(const QString &item); diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 0fb1b42161..5f3575eb40 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -13,7 +13,6 @@ ContentSelectorModel::ContentModel::ContentModel(QObject *parent) : mMimeTypes (QStringList() << mMimeType), mColumnCount (1), mDragDropFlags (Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled), - mDefaultFlags (Qt::ItemIsDropEnabled | Qt::ItemIsSelectable), mDropActions (Qt::CopyAction | Qt::MoveAction) { setEncoding ("win1252"); @@ -102,10 +101,53 @@ Qt::ItemFlags ContentSelectorModel::ContentModel::flags(const QModelIndex &index if (!file) return Qt::NoItemFlags; - if (canBeChecked(file)) - return Qt::ItemIsEnabled | mDragDropFlags | mDefaultFlags; + //game files can always be checked + if (file->isGameFile()) + return Qt::ItemIsEnabled | Qt::ItemIsSelectable; - return Qt::NoItemFlags; + Qt::ItemFlags returnFlags; + bool allDependenciesFound = true; + bool gamefileChecked = false; + + //addon can be checked if its gamefile is and all other dependencies exist + foreach (const QString &fileName, file->gameFiles()) + { + bool depFound = false; + foreach (EsmFile *dependency, mFiles) + { + //compare filenames only. Multiple instances + //of the filename (with different paths) is not relevant here. + depFound = (dependency->fileName() == fileName); + + if (!depFound) + continue; + + if (!gamefileChecked) + { + if (isChecked (dependency->filePath())) + gamefileChecked = (dependency->isGameFile()); + } + + // force it to iterate all files in cases where the current + // dependency is a game file to ensure that a later duplicate + // game file is / is not checked. + // (i.e., break only if it's not a gamefile or the game file has been checked previously) + if (gamefileChecked || !(dependency->isGameFile())) + break; + } + + allDependenciesFound = allDependenciesFound && depFound; + } + + if (gamefileChecked) + { + if (allDependenciesFound) + returnFlags = returnFlags | Qt::ItemIsEnabled | Qt::ItemIsSelectable | mDragDropFlags; + else + returnFlags = Qt::ItemIsSelectable; + } + + return returnFlags; } QVariant ContentSelectorModel::ContentModel::data(const QModelIndex &index, int role) const @@ -173,7 +215,7 @@ QVariant ContentSelectorModel::ContentModel::data(const QModelIndex &index, int if (file->isGameFile()) return ContentType_GameFile; else - if (flags(index) & mDefaultFlags) + if (flags(index)) return ContentType_Addon; break; @@ -215,19 +257,13 @@ bool ContentSelectorModel::ContentModel::setData(const QModelIndex &index, const case Qt::UserRole+1: { - setCheckState(fileName, value.toBool()); + success = (flags (index) & Qt::ItemIsEnabled); - emit dataChanged(index, index); - - foreach (EsmFile *file, mFiles) + if (success) { - if (file->gameFiles().contains(fileName)) - { - QModelIndex idx = indexFromItem(file); - emit dataChanged(idx, idx); - } + success = setCheckState(fileName, value.toBool()); + emit dataChanged(index, index); } - success = true; } break; @@ -377,32 +413,6 @@ bool ContentSelectorModel::ContentModel::dropMimeData(const QMimeData *data, Qt: return true; } -bool ContentSelectorModel::ContentModel::canBeChecked(const EsmFile *file) const -{ - //game files can always be checked - if (file->isGameFile()) - return true; - - //addon can be checked if its gamefile is - foreach (const QString &fileName, file->gameFiles()) - { - foreach (EsmFile *dependency, mFiles) - { - //compare filenames only. Multiple instances - //of the filename (with different paths) is not relevant here. - if (!(dependency->fileName() == fileName)) - continue; - - if (dependency->isGameFile()) - { - if (isChecked(dependency->filePath())) - return true; - } - } - } - return false; -} - void ContentSelectorModel::ContentModel::addFile(EsmFile *file) { beginInsertRows(QModelIndex(), mFiles.count(), mFiles.count()); @@ -510,6 +520,11 @@ bool ContentSelectorModel::ContentModel::isChecked(const QString& name) const return false; } +bool ContentSelectorModel::ContentModel::isEnabled (QModelIndex index) const +{ + return (flags(index) & Qt::ItemIsEnabled); +} + void ContentSelectorModel::ContentModel::setCheckStates (const QStringList &fileList, bool isChecked) { foreach (const QString &file, fileList) @@ -518,6 +533,11 @@ void ContentSelectorModel::ContentModel::setCheckStates (const QStringList &file } } +void ContentSelectorModel::ContentModel::refreshModel() +{ + emit dataChanged (index(0,0), index(rowCount()-1,0)); +} + bool ContentSelectorModel::ContentModel::setCheckState(const QString &name, bool checkState) { if (name.isEmpty()) @@ -537,7 +557,7 @@ bool ContentSelectorModel::ContentModel::setCheckState(const QString &name, bool emit dataChanged(indexFromItem(item(name)), indexFromItem(item(name))); if (file->isGameFile()) - emit dataChanged (index(0,0), index(rowCount()-1,0)); + refreshModel(); //if we're checking an item, ensure all "upstream" files (dependencies) are checked as well. if (state == Qt::Checked) diff --git a/components/contentselector/model/contentmodel.hpp b/components/contentselector/model/contentmodel.hpp index ae49dd27da..8c8c2124bc 100644 --- a/components/contentselector/model/contentmodel.hpp +++ b/components/contentselector/model/contentmodel.hpp @@ -44,19 +44,21 @@ namespace ContentSelectorModel QModelIndex indexFromItem(const EsmFile *item) const; const EsmFile *item(const QString &name) const; + bool isEnabled (QModelIndex index) const; bool isChecked(const QString &name) const; bool setCheckState(const QString &name, bool isChecked); void setCheckStates (const QStringList &fileList, bool isChecked); ContentFileList checkedItems() const; void uncheckAll(); + void refreshModel(); + private: void addFile(EsmFile *file); const EsmFile *item(int row) const; EsmFile *item(int row); - bool canBeChecked(const EsmFile *file) const; void sortFiles(); ContentFileList mFiles; @@ -69,7 +71,6 @@ namespace ContentSelectorModel QStringList mMimeTypes; int mColumnCount; Qt::ItemFlags mDragDropFlags; - Qt::ItemFlags mDefaultFlags; Qt::DropActions mDropActions; }; } diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index 4758dd5a05..b962fa9ce6 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -12,6 +12,8 @@ #include #include +#include + ContentSelectorView::ContentSelector::ContentSelector(QWidget *parent) : QObject(parent) { @@ -89,9 +91,12 @@ void ContentSelectorView::ContentSelector::clearCheckStates() void ContentSelectorView::ContentSelector::setCheckStates(const QStringList &list) { if (list.isEmpty()) - return; - - mContentModel->setCheckStates (list, true); + { + qDebug() << "refreshing model"; + slotCurrentGameFileIndexChanged (ui.gameFileView->currentIndex()); + } + else + mContentModel->setCheckStates (list, true); } ContentSelectorModel::ContentFileList @@ -152,14 +157,17 @@ void ContentSelectorView::ContentSelector::slotCurrentGameFileIndexChanged(int i void ContentSelectorView::ContentSelector::slotAddonTableItemClicked(const QModelIndex &index) { - QAbstractItemModel *const model = ui.addonView->model(); + QModelIndex sourceIndex = mAddonProxyModel->mapToSource (index); + + if (!mContentModel->isEnabled (sourceIndex)) + return; Qt::CheckState checkState = Qt::Unchecked; - if (model->data(index, Qt::CheckStateRole).toInt() == Qt::Unchecked) + if (mContentModel->data(sourceIndex, Qt::CheckStateRole).toInt() == Qt::Unchecked) checkState = Qt::Checked; - model->setData(index, checkState, Qt::CheckStateRole); + mContentModel->setData(sourceIndex, checkState, Qt::CheckStateRole); if (checkState == Qt::Checked) emit signalAddonFileSelected (index.row()); From 3ac58b387b1cbc07ec76a5d82fe77008dc6fd4b2 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 3 Nov 2013 22:08:38 +0100 Subject: [PATCH 167/434] fixed wording of an error message --- apps/launcher/maindialog.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 273494ff3a..4012a1fbd5 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -496,12 +496,12 @@ bool Launcher::MainDialog::setupGameSettings() QAbstractButton *dirSelectButton = msgBox.addButton(QObject::tr("Browse to &Install..."), QMessageBox::ActionRole); - + #ifndef WIN32 - QAbstractButton *cdSelectButton = + QAbstractButton *cdSelectButton = msgBox.addButton(QObject::tr("Browse to &CD..."), QMessageBox::ActionRole); #endif - + msgBox.exec(); @@ -516,14 +516,14 @@ bool Launcher::MainDialog::setupGameSettings() #ifndef WIN32 else if(msgBox.clickedButton() == cdSelectButton) { UnshieldThread cd; - + { TextSlotMsgBox cdbox; - cdbox.setStandardButtons(QMessageBox::Cancel); + cdbox.setStandardButtons(QMessageBox::Cancel); QObject::connect(&cd,SIGNAL(signalGUI(const QString&)), &cdbox, SLOT(setTextSlot(const QString&))); QObject::connect(&cd,SIGNAL(close()), &cdbox, SLOT(reject())); - + cd.SetMorrowindPath( QFileDialog::getOpenFileName( NULL, @@ -537,11 +537,11 @@ bool Launcher::MainDialog::setupGameSettings() QObject::tr("Select where to extract files to"), QDir::currentPath(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks).toUtf8().constData()); - + cd.start(); cdbox.exec(); } - + while(expansions(cd)); selectedFile = QString::fromStdString(cd.GetMWEsmPath()); @@ -753,11 +753,11 @@ void Launcher::MainDialog::play() if(!mGameSettings.hasMaster()) { QMessageBox msgBox; - msgBox.setWindowTitle(tr("No master file selected")); + msgBox.setWindowTitle(tr("No game file selected")); msgBox.setIcon(QMessageBox::Warning); msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
You do not have any master files selected.

\ - OpenMW will not start without a master file selected.
")); + msgBox.setText(tr("
You do not have no game file selected.

\ + OpenMW will not start without a game file selected.
")); msgBox.exec(); return; } From dace9044900a94eadddeea0442182fad7b152452 Mon Sep 17 00:00:00 2001 From: graffy76 Date: Sun, 3 Nov 2013 16:45:18 -0600 Subject: [PATCH 168/434] changed game/addon to content for writing to openmw.cfg from launcher --- apps/launcher/datafilespage.cpp | 4 ++-- apps/opencs/editor.cpp | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 5ae850566f..5452b73985 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -104,10 +104,10 @@ void Launcher::DataFilesPage::saveSettings(const QString &profile) if (item->gameFiles().size() == 0) { mLauncherSettings.setMultiValue(QString("Profiles/") + profileName + QString("/game"), item->fileName()); - mGameSettings.setMultiValue(QString("game"), item->fileName()); + mGameSettings.setMultiValue(QString("content"), item->fileName()); } else { mLauncherSettings.setMultiValue(QString("Profiles/") + profileName + QString("/addon"), item->fileName()); - mGameSettings.setMultiValue(QString("addon"), item->fileName()); + mGameSettings.setMultiValue(QString("content"), item->fileName()); } } diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 63afe7a9dd..e879cb25e8 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -94,6 +94,7 @@ void CS::Editor::setupDataFiles() for (Files::PathContainer::const_iterator iter = dataDirs.begin(); iter != dataDirs.end(); ++iter) { + QString path = QString::fromStdString(iter->string()); mFileDialog.addFiles(path); } From ed913936f8544d7d1b646e9454e9c26b5f860e3f Mon Sep 17 00:00:00 2001 From: graffy76 Date: Sun, 3 Nov 2013 21:36:41 -0600 Subject: [PATCH 169/434] Eliminated game & addon keys from profile configuration --- apps/launcher/datafilespage.cpp | 65 ++++++++----------- apps/launcher/settings/gamesettings.cpp | 1 + apps/launcher/settings/launchersettings.cpp | 5 +- .../contentselector/view/contentselector.cpp | 32 ++++++++- .../contentselector/view/contentselector.hpp | 1 + 5 files changed, 59 insertions(+), 45 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 5452b73985..734277b97d 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -42,44 +42,19 @@ void Launcher::DataFilesPage::loadSettings() QString profileName = ui.profilesComboBox->currentText(); - QStringList files = mLauncherSettings.values(QString("Profiles/") + profileName + QString("/game"), Qt::MatchExactly); - QStringList addons = mLauncherSettings.values(QString("Profiles/") + profileName + QString("/addon"), Qt::MatchExactly); + QStringList files = mLauncherSettings.values(QString("Profiles/") + profileName, Qt::MatchExactly); - mSelector->clearCheckStates(); + QStringList filepaths; - QString gameFile (""); - - if (files.size()>0) + foreach (const QString &file, files) { - gameFile = pathIterator.findFirstPath (files.at(0)); + QString filepath = pathIterator.findFirstPath (file); - if (!gameFile.isEmpty()) - mSelector->setGameFile (gameFile); -/* else - { - //throw gamefile error here. - }*/ + if (!filepath.isEmpty()) + filepaths << filepath; } - QStringList missingFiles; - QStringList foundFiles; - - foreach (const QString &addon, addons) - { - QString filePath = pathIterator.findFirstPath (addon); - - if (filePath.isEmpty()) - missingFiles << addon; - else - foundFiles << filePath; - } -/* - if (missingFiles.size() > 0) - { - //throw addons error here. - } -*/ - mSelector->setCheckStates (foundFiles); + mSelector->setProfileContent (filepaths); } void Launcher::DataFilesPage::saveSettings(const QString &profile) @@ -94,8 +69,7 @@ void Launcher::DataFilesPage::saveSettings(const QString &profile) removeProfile (profileName); - mGameSettings.remove(QString("game")); - mGameSettings.remove(QString("addon")); + mGameSettings.remove(QString("content")); //set the value of the current profile (not necessarily the profile being saved!) mLauncherSettings.setValue(QString("Profiles/currentprofile"), ui.profilesComboBox->currentText()); @@ -103,10 +77,10 @@ void Launcher::DataFilesPage::saveSettings(const QString &profile) foreach(const ContentSelectorModel::EsmFile *item, items) { if (item->gameFiles().size() == 0) { - mLauncherSettings.setMultiValue(QString("Profiles/") + profileName + QString("/game"), item->fileName()); + mLauncherSettings.setMultiValue(QString("Profiles/") + profileName, item->fileName()); mGameSettings.setMultiValue(QString("content"), item->fileName()); } else { - mLauncherSettings.setMultiValue(QString("Profiles/") + profileName + QString("/addon"), item->fileName()); + mLauncherSettings.setMultiValue(QString("Profiles/") + profileName, item->fileName()); mGameSettings.setMultiValue(QString("content"), item->fileName()); } } @@ -225,13 +199,26 @@ void Launcher::DataFilesPage::setupDataFiles() if (!mDataLocal.isEmpty()) mSelector->addFiles(mDataLocal); - QStringList profiles = mLauncherSettings.subKeys(QString("Profiles/")); - QString profile = mLauncherSettings.value(QString("Profiles/currentprofile")); + QStringList profiles; + QString currentProfile = mLauncherSettings.getSettings().value("Profiles/currentprofile"); + + foreach (QString key, mLauncherSettings.getSettings().keys()) + { + if (key.contains("Profiles/")) + { + QString profile = key.mid (9); + if (profile != "currentprofile") + { + if (!profiles.contains(profile)) + profiles << profile; + } + } + } foreach (const QString &item, profiles) addProfile (item, false); - setProfile (ui.profilesComboBox->findText(profile), false); + setProfile (ui.profilesComboBox->findText(currentProfile), false); loadSettings(); } diff --git a/apps/launcher/settings/gamesettings.cpp b/apps/launcher/settings/gamesettings.cpp index 83c0995291..41113c35aa 100644 --- a/apps/launcher/settings/gamesettings.cpp +++ b/apps/launcher/settings/gamesettings.cpp @@ -9,6 +9,7 @@ #include #include + /** * Workaround for problems with whitespaces in paths in older versions of Boost library */ diff --git a/apps/launcher/settings/launchersettings.cpp b/apps/launcher/settings/launchersettings.cpp index 7c97144eaf..705453555d 100644 --- a/apps/launcher/settings/launchersettings.cpp +++ b/apps/launcher/settings/launchersettings.cpp @@ -5,6 +5,8 @@ #include #include +#include + Launcher::LauncherSettings::LauncherSettings() { } @@ -44,12 +46,9 @@ QStringList Launcher::LauncherSettings::subKeys(const QString &key) QStringList result; foreach (const QString ¤tKey, keys) { - if (keyRe.indexIn(currentKey) != -1) { - QString prefixedKey = keyRe.cap(1); if(prefixedKey.startsWith(key)) { - QString subKey = prefixedKey.remove(key); if (!subKey.isEmpty()) result.append(subKey); diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index b962fa9ce6..e9599de498 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -12,8 +12,6 @@ #include #include -#include - ContentSelectorView::ContentSelector::ContentSelector(QWidget *parent) : QObject(parent) { @@ -63,6 +61,35 @@ void ContentSelectorView::ContentSelector::buildAddonView() connect(ui.addonView, SIGNAL(clicked(const QModelIndex &)), this, SLOT(slotAddonTableItemClicked(const QModelIndex &))); } +void ContentSelectorView::ContentSelector::setProfileContent(const QStringList &fileList) +{ + clearCheckStates(); + bool foundGamefile = false; + + foreach (const QString &filepath, fileList) + { + if (!foundGamefile) + { + const ContentSelectorModel::EsmFile *file = mContentModel->item(filepath); + + foundGamefile = (file->isGameFile()); + + if (foundGamefile) + { + setGameFile (filepath); + break; + } + } + } + +/* if (!foundGameFile) + { + //throw gamefile error here. + }*/ + + setCheckStates (fileList); +} + void ContentSelectorView::ContentSelector::setGameFile(const QString &filename) { int index = -1; @@ -92,7 +119,6 @@ void ContentSelectorView::ContentSelector::setCheckStates(const QStringList &lis { if (list.isEmpty()) { - qDebug() << "refreshing model"; slotCurrentGameFileIndexChanged (ui.gameFileView->currentIndex()); } else diff --git a/components/contentselector/view/contentselector.hpp b/components/contentselector/view/contentselector.hpp index da1c39973d..a25eb20ae3 100644 --- a/components/contentselector/view/contentselector.hpp +++ b/components/contentselector/view/contentselector.hpp @@ -29,6 +29,7 @@ namespace ContentSelectorView QString currentFile() const; void addFiles(const QString &path); + void setProfileContent (const QStringList &fileList); void clearCheckStates(); void setCheckStates (const QStringList &list); From 8e439c290d616b1f6af2488bf9e3be4cb54f42e5 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 4 Nov 2013 09:22:23 +0100 Subject: [PATCH 170/434] updated changelog --- readme.txt | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/readme.txt b/readme.txt index c4d9d27461..a790ca9e35 100644 --- a/readme.txt +++ b/readme.txt @@ -82,6 +82,31 @@ Allowed options: CHANGELOG +0.27.0 + +Bug #597: Assertion `dialogue->mId == id' failed in esmstore.cpp +Bug #794: incorrect display of decimal numbers +Bug #840: First-person sneaking camera height +Bug #887: Ambient sounds playing while paused +Bug #902: Problems with Polish character encoding +Bug #907: Entering third person using the mousewheel is possible even if it's impossible using the key +Bug #917: Quick character creation plugin does not work +Bug #918: Fatigue does not refill +Bug #919: The PC falls dead in Beshara - OpenMW nightly Win64 (708CDE2) +Feature #57: Acrobatics Skill +Feature #462: Editor: Start Dialogue +Feature #546: Modify ESX selector to handle new content file scheme +Feature #588: Editor: Adjust name/path of edited content files +Feature #644: Editor: Save +Feature #710: Editor: Configure script compiler context +Feature #790: God Mode +Feature #881: Editor: Allow only one instance of OpenCS +Feature #889: Editor: Record filtering +Feature #895: Extinguish torches +Feature #898: Breath meter enhancements +Feature #901: Editor: Default record filter +Feature #913: Merge --master and --plugin switches + 0.26.0 Bug #274: Inconsistencies in the terrain From 468e8e3635d6532fb334d17cabd5b7bf52a5ecc1 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Mon, 4 Nov 2013 10:36:22 +0100 Subject: [PATCH 171/434] Missing iostream include --- apps/openmw/mwworld/contentloader.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwworld/contentloader.hpp b/apps/openmw/mwworld/contentloader.hpp index c57935c907..46bd7d3f96 100644 --- a/apps/openmw/mwworld/contentloader.hpp +++ b/apps/openmw/mwworld/contentloader.hpp @@ -2,6 +2,7 @@ #define CONTENTLOADER_HPP #include +#include #include #include "components/loadinglistener/loadinglistener.hpp" From 8bacdc8ab8910f613fa7de32849998e31218bad7 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 4 Nov 2013 14:19:58 +0100 Subject: [PATCH 172/434] updated changelog --- readme.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.txt b/readme.txt index a790ca9e35..afcfadea3d 100644 --- a/readme.txt +++ b/readme.txt @@ -90,6 +90,7 @@ Bug #840: First-person sneaking camera height Bug #887: Ambient sounds playing while paused Bug #902: Problems with Polish character encoding Bug #907: Entering third person using the mousewheel is possible even if it's impossible using the key +Bug #910: Some CDs not working correctly with Unshield installer Bug #917: Quick character creation plugin does not work Bug #918: Fatigue does not refill Bug #919: The PC falls dead in Beshara - OpenMW nightly Win64 (708CDE2) From 3a827d9c12d2cdedf7eee03b17a348fbbaa96669 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 5 Nov 2013 00:32:06 +0100 Subject: [PATCH 173/434] Deleted Obliviontt.zip --- Daedric Font License.txt | 10 ---------- apps/openmw/engine.cpp | 1 - files/mygui/CMakeLists.txt | 1 - files/mygui/Obliviontt.zip | Bin 138502 -> 0 bytes 4 files changed, 12 deletions(-) delete mode 100644 Daedric Font License.txt delete mode 100644 files/mygui/Obliviontt.zip diff --git a/Daedric Font License.txt b/Daedric Font License.txt deleted file mode 100644 index a1553d0b04..0000000000 --- a/Daedric Font License.txt +++ /dev/null @@ -1,10 +0,0 @@ -Dongle's Oblivion Daedric font set -http://www.uesp.net/wiki/Lore:Daedric_Alphabet#Daedric_Font - ---------------------------------------------------- - -This was done entirely as a personal project. Bethesda Softworks graciously granted me the permission for it. I am not connected with them in any way. -You may freely use these fonts to create anything you'd like. You may re-distribute the fonts freely, over the Internet, or by any other means. Always keep the .zip file intact, and this read me included. -Please do not modify and redistribute the fonts without my permission. -You may NOT sell any of these fonts under any circumstances. This includes putting them on compilation font CDs for sale, putting them in a "members only" pay-area of a website, or any other means of financial gain connected in ANY way with the redistribution of any of these fonts. -You have my permission to create and sell any artwork made with these fonts, however you may need to contact Bethesda Softworks before doing so. diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index e1fd3a0af3..35ff6593b3 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -370,7 +370,6 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) addResourcesDirectory(mResDir / "mygui"); addResourcesDirectory(mResDir / "water"); addResourcesDirectory(mResDir / "shadows"); - addZipResource(mResDir / "mygui" / "Obliviontt.zip"); OEngine::Render::WindowSettings windowSettings; windowSettings.fullscreen = settings.getBool("fullscreen", "Video"); diff --git a/files/mygui/CMakeLists.txt b/files/mygui/CMakeLists.txt index 1ec1e08cb5..21153a4740 100644 --- a/files/mygui/CMakeLists.txt +++ b/files/mygui/CMakeLists.txt @@ -9,7 +9,6 @@ set(MYGUI_FILES core.skin core.xml EBGaramond-Regular.ttf - Obliviontt.zip openmw_alchemy_window.layout openmw_book.layout openmw_box.skin.xml diff --git a/files/mygui/Obliviontt.zip b/files/mygui/Obliviontt.zip deleted file mode 100644 index af4f809fd846cff444d5b1f09c7e86df1b6207d1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 138502 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⁡xddcV`NI&{-&A;hf~zQpRC??m3?0ZBLV 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 Z|2M=R(QxzpFvfG(^Em(jU#TDd{1aQG&olr4 From c3d9bc31bbcdee7418eb888ccb175e9dd9a6b599 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 5 Nov 2013 03:02:28 +0100 Subject: [PATCH 174/434] Fix exiting OpenMW via the window close button (regression) --- apps/openmw/mwinput/inputmanagerimp.cpp | 5 +++++ apps/openmw/mwinput/inputmanagerimp.hpp | 1 + extern/sdl4ogre/events.h | 2 ++ extern/sdl4ogre/sdlinputwrapper.cpp | 3 ++- 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index c4a360cd4b..f9ea3dfc3e 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -641,6 +641,11 @@ namespace MWInput mOgre.windowResized(x,y); } + void InputManager::windowClosed() + { + MWBase::Environment::setRequestExit(); + } + void InputManager::toggleMainMenu() { if (MyGUI::InputManager::getInstance ().isModalAny()) diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index b38c80e0e2..e7b7d6c7f5 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -97,6 +97,7 @@ namespace MWInput virtual void windowVisibilityChange( bool visible ); virtual void windowFocusChange( bool have_focus ); virtual void windowResized (int x, int y); + virtual void windowClosed (); virtual void channelChanged(ICS::Channel* channel, float currentValue, float previousValue); diff --git a/extern/sdl4ogre/events.h b/extern/sdl4ogre/events.h index e6e8434cb9..48adb45456 100644 --- a/extern/sdl4ogre/events.h +++ b/extern/sdl4ogre/events.h @@ -70,6 +70,8 @@ public: /** @remarks The window got / lost input focus */ virtual void windowFocusChange( bool have_focus ) {} + virtual void windowClosed () {} + virtual void windowResized (int x, int y) {} }; diff --git a/extern/sdl4ogre/sdlinputwrapper.cpp b/extern/sdl4ogre/sdlinputwrapper.cpp index df74bba3b6..e7cbd549cb 100644 --- a/extern/sdl4ogre/sdlinputwrapper.cpp +++ b/extern/sdl4ogre/sdlinputwrapper.cpp @@ -103,7 +103,8 @@ namespace SFO handleWindowEvent(evt); break; case SDL_QUIT: - Ogre::Root::getSingleton().queueEndRendering(); + if (mWindowListener) + mWindowListener->windowClosed(); break; default: std::cerr << "Unhandled SDL event of type " << evt.type << std::endl; From 9d7695ea88cd260da9358cf928ec84459ddb2a07 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 5 Nov 2013 11:41:48 +0100 Subject: [PATCH 175/434] added missing columns to topic info table --- apps/esmtool/record.cpp | 4 +- apps/opencs/model/world/columnbase.hpp | 3 +- apps/opencs/model/world/columnimp.hpp | 231 ++++++++++++++++++++++++- apps/opencs/model/world/columns.cpp | 15 +- apps/opencs/model/world/columns.hpp | 7 + apps/opencs/model/world/data.cpp | 12 ++ apps/opencs/model/world/ref.cpp | 2 +- apps/opencs/model/world/ref.hpp | 2 +- apps/opencs/view/doc/viewmanager.cpp | 3 +- apps/openmw/mwdialogue/filter.cpp | 4 +- components/esm/loadinfo.cpp | 8 +- components/esm/loadinfo.hpp | 2 +- 12 files changed, 277 insertions(+), 16 deletions(-) diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index 68e0dcc09d..e9fb1c5379 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -740,8 +740,8 @@ void Record::print() if (mData.mClass != "") std::cout << " Class: " << mData.mClass << std::endl; std::cout << " Factionless: " << mData.mFactionLess << std::endl; - if (mData.mNpcFaction != "") - std::cout << " NPC Faction: " << mData.mNpcFaction << std::endl; + if (mData.mFaction != "") + std::cout << " NPC Faction: " << mData.mFaction << std::endl; if (mData.mData.mRank != -1) std::cout << " NPC Rank: " << (int)mData.mData.mRank << std::endl; if (mData.mPcFaction != "") diff --git a/apps/opencs/model/world/columnbase.hpp b/apps/opencs/model/world/columnbase.hpp index 2c3408a013..70f38c5341 100644 --- a/apps/opencs/model/world/columnbase.hpp +++ b/apps/opencs/model/world/columnbase.hpp @@ -45,7 +45,8 @@ namespace CSMWorld Display_RecordState, Display_RefRecordType, Display_DialogueType, - Display_QuestStatusType + Display_QuestStatusType, + Display_Gender }; int mColumnId; diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index 5d9fe9a1b1..a2994ec647 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -815,14 +815,14 @@ namespace CSMWorld virtual QVariant get (const Record& record) const { - return QString::fromUtf8 (record.get().mCellId.c_str()); + return QString::fromUtf8 (record.get().mCell.c_str()); } virtual void set (Record& record, const QVariant& data) { ESXRecordT record2 = record.get(); - record2.mCellId = data.toString().toUtf8().constData(); + record2.mCell = data.toString().toUtf8().constData(); record.setModified (record2); } @@ -1425,6 +1425,233 @@ namespace CSMWorld return false; } }; + + template + struct ActorColumn : public Column + { + ActorColumn() : Column (Columns::ColumnId_Actor, ColumnBase::Display_String) {} + + virtual QVariant get (const Record& record) const + { + return QString::fromUtf8 (record.get().mActor.c_str()); + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + + record2.mActor = data.toString().toUtf8().constData(); + + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + }; + + template + struct RaceColumn : public Column + { + RaceColumn() : Column (Columns::ColumnId_Race, ColumnBase::Display_String) {} + + virtual QVariant get (const Record& record) const + { + return QString::fromUtf8 (record.get().mRace.c_str()); + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + + record2.mRace = data.toString().toUtf8().constData(); + + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + }; + + template + struct ClassColumn : public Column + { + ClassColumn() : Column (Columns::ColumnId_Class, ColumnBase::Display_String) {} + + virtual QVariant get (const Record& record) const + { + return QString::fromUtf8 (record.get().mClass.c_str()); + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + + record2.mClass = data.toString().toUtf8().constData(); + + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + }; + + template + struct PcFactionColumn : public Column + { + PcFactionColumn() : Column (Columns::ColumnId_PcFaction, ColumnBase::Display_String) {} + + virtual QVariant get (const Record& record) const + { + return QString::fromUtf8 (record.get().mPcFaction.c_str()); + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + + record2.mPcFaction = data.toString().toUtf8().constData(); + + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + }; + + template + struct ResponseColumn : public Column + { + ResponseColumn() : Column (Columns::ColumnId_Response, ColumnBase::Display_String) {} + + virtual QVariant get (const Record& record) const + { + return QString::fromUtf8 (record.get().mResponse.c_str()); + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + + record2.mResponse = data.toString().toUtf8().constData(); + + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + }; + + template + struct DispositionColumn : public Column + { + DispositionColumn() + : Column (Columns::ColumnId_Disposition, ColumnBase::Display_Integer) + {} + + virtual QVariant get (const Record& record) const + { + return record.get().mData.mDisposition; + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + record2.mData.mDisposition = data.toInt(); + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + }; + + template + struct RankColumn : public Column + { + RankColumn() + : Column (Columns::ColumnId_Rank, ColumnBase::Display_Integer) + {} + + virtual QVariant get (const Record& record) const + { + return static_cast (record.get().mData.mRank); + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + record2.mData.mRank = static_cast (data.toInt()); + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + }; + + template + struct PcRankColumn : public Column + { + PcRankColumn() + : Column (Columns::ColumnId_PcRank, ColumnBase::Display_Integer) + {} + + virtual QVariant get (const Record& record) const + { + return static_cast (record.get().mData.mPCrank); + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + record2.mData.mPCrank = static_cast (data.toInt()); + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + }; + + template + struct GenderColumn : public Column + { + GenderColumn() + : Column (Columns::ColumnId_Gender, ColumnBase::Display_Gender) + {} + + virtual QVariant get (const Record& record) const + { + return static_cast (record.get().mData.mGender); + } + + virtual void set (Record& record, const QVariant& data) + { + ESXRecordT record2 = record.get(); + + record2.mData.mGender = data.toInt(); + + record.setModified (record2); + } + + virtual bool isEditable() const + { + return true; + } + }; } #endif diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index 286afc64d4..980d1f75d6 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -164,7 +164,14 @@ namespace CSMWorld { ColumnId_QuestStatusType, "Quest Status" }, { ColumnId_QuestDescription, "Quest Description" }, { ColumnId_Topic, "Topic" }, - { ColumnId_Journal, "Journal", }, + { ColumnId_Journal, "Journal" }, + { ColumnId_Actor, "Actor" }, + { ColumnId_PcFaction, "PC Faction" }, + { ColumnId_Response, "Response" }, + { ColumnId_Disposition, "Disposition" }, + { ColumnId_Rank, "Rank" }, + { ColumnId_Gender, "Gender" }, + { ColumnId_PcRank, "PC Rank" }, { ColumnId_UseValue1, "Use value 1" }, { ColumnId_UseValue2, "Use value 2" }, @@ -285,6 +292,11 @@ namespace "None", "Name", "Finished", "Restart", 0 }; + static const char *sGenderEnums[] = + { + "Male", "Female", 0 + }; + const char **getEnumNames (CSMWorld::Columns::ColumnId column) { switch (column) @@ -301,6 +313,7 @@ namespace case CSMWorld::Columns::ColumnId_ValueType: return sVarTypeEnums; case CSMWorld::Columns::ColumnId_DialogueType: return sDialogueTypeEnums; case CSMWorld::Columns::ColumnId_QuestStatusType: return sQuestStatusTypes; + case CSMWorld::Columns::ColumnId_Gender: return sGenderEnums; default: return 0; } diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index 6965d4c313..056cc4fa12 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -158,6 +158,13 @@ namespace CSMWorld ColumnId_QuestDescription = 145, ColumnId_Topic = 146, ColumnId_Journal = 147, + ColumnId_Actor = 148, + ColumnId_PcFaction = 149, + ColumnId_Response = 150, + ColumnId_Disposition = 151, + ColumnId_Rank = 152, + ColumnId_Gender = 153, + ColumnId_PcRank = 154, // Allocated to a separate value range, so we don't get a collision should we ever need // to extend the number of use values. diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 88fb9fdedb..dc9feab51b 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -152,6 +152,18 @@ CSMWorld::Data::Data() : mRefs (mCells) mTopicInfos.addColumn (new StringIdColumn); mTopicInfos.addColumn (new RecordStateColumn); mTopicInfos.addColumn (new TopicColumn (false)); + mTopicInfos.addColumn (new ActorColumn); + mTopicInfos.addColumn (new RaceColumn); + mTopicInfos.addColumn (new ClassColumn); + mTopicInfos.addColumn (new FactionColumn); + mTopicInfos.addColumn (new CellColumn); + mTopicInfos.addColumn (new DispositionColumn); + mTopicInfos.addColumn (new RankColumn); + mTopicInfos.addColumn (new GenderColumn); + mTopicInfos.addColumn (new PcFactionColumn); + mTopicInfos.addColumn (new PcRankColumn); + mTopicInfos.addColumn (new SoundFileColumn); + mTopicInfos.addColumn (new ResponseColumn); mJournalInfos.addColumn (new StringIdColumn); mJournalInfos.addColumn (new RecordStateColumn); diff --git a/apps/opencs/model/world/ref.cpp b/apps/opencs/model/world/ref.cpp index af363bafb4..74f60419b9 100644 --- a/apps/opencs/model/world/ref.cpp +++ b/apps/opencs/model/world/ref.cpp @@ -6,7 +6,7 @@ void CSMWorld::CellRef::load (ESM::ESMReader &esm, Cell& cell, const std::string& id) { mId = id; - mCellId = cell.mId; + mCell = cell.mId; if (!mDeleted) cell.addRef (mId); diff --git a/apps/opencs/model/world/ref.hpp b/apps/opencs/model/world/ref.hpp index 3d107d6754..fcf016ee24 100644 --- a/apps/opencs/model/world/ref.hpp +++ b/apps/opencs/model/world/ref.hpp @@ -16,7 +16,7 @@ namespace CSMWorld struct CellRef : public ESM::CellRef { std::string mId; - std::string mCellId; + std::string mCell; void load (ESM::ESMReader &esm, Cell& cell, const std::string& id); ///< Load cell ref and register it with \a cell. diff --git a/apps/opencs/view/doc/viewmanager.cpp b/apps/opencs/view/doc/viewmanager.cpp index b4b55dea0d..4a4dbc1244 100644 --- a/apps/opencs/view/doc/viewmanager.cpp +++ b/apps/opencs/view/doc/viewmanager.cpp @@ -76,7 +76,8 @@ CSVDoc::ViewManager::ViewManager (CSMDoc::DocumentManager& documentManager) { CSMWorld::ColumnBase::Display_CreatureType, CSMWorld::Columns::ColumnId_CreatureType, false }, { CSMWorld::ColumnBase::Display_WeaponType, CSMWorld::Columns::ColumnId_WeaponType, false }, { CSMWorld::ColumnBase::Display_DialogueType, CSMWorld::Columns::ColumnId_DialogueType, false }, - { CSMWorld::ColumnBase::Display_QuestStatusType, CSMWorld::Columns::ColumnId_QuestStatusType, false } + { CSMWorld::ColumnBase::Display_QuestStatusType, CSMWorld::Columns::ColumnId_QuestStatusType, false }, + { CSMWorld::ColumnBase::Display_Gender, CSMWorld::Columns::ColumnId_Gender, true } }; for (std::size_t i=0; i::iterator iter = stats.getFactionRanks().find ( Misc::StringUtils::lowerCase (info.mNpcFaction)); + std::map::iterator iter = stats.getFactionRanks().find ( Misc::StringUtils::lowerCase (info.mFaction)); if (iter==stats.getFactionRanks().end()) return false; diff --git a/components/esm/loadinfo.cpp b/components/esm/loadinfo.cpp index f248e0784c..be07e51282 100644 --- a/components/esm/loadinfo.cpp +++ b/components/esm/loadinfo.cpp @@ -48,8 +48,8 @@ void DialInfo::load(ESMReader &esm) mFactionLess = false; if (subName.val == REC_FNAM) { - mNpcFaction = esm.getHString(); - if (mNpcFaction == "FFFF") + mFaction = esm.getHString(); + if (mFaction == "FFFF") mFactionLess = true; if (esm.isEmptyOrGetName()) return; @@ -129,7 +129,7 @@ void DialInfo::save(ESMWriter &esm) esm.writeHNOCString("ONAM", mActor); esm.writeHNOCString("RNAM", mRace); esm.writeHNOCString("CNAM", mClass); - esm.writeHNOCString("FNAM", mNpcFaction); + esm.writeHNOCString("FNAM", mFaction); esm.writeHNOCString("ANAM", mCell); esm.writeHNOCString("DNAM", mPcFaction); esm.writeHNOCString("SNAM", mSound); @@ -168,7 +168,7 @@ void DialInfo::save(ESMWriter &esm) mActor.clear(); mRace.clear(); mClass.clear(); - mNpcFaction.clear(); + mFaction.clear(); mPcFaction.clear(); mCell.clear(); mSound.clear(); diff --git a/components/esm/loadinfo.hpp b/components/esm/loadinfo.hpp index 7ec3e84bbf..469d5805ef 100644 --- a/components/esm/loadinfo.hpp +++ b/components/esm/loadinfo.hpp @@ -63,7 +63,7 @@ struct DialInfo std::string mId, mPrev, mNext; // Various references used in determining when to select this item. - std::string mActor, mRace, mClass, mNpcFaction, mPcFaction, mCell; + std::string mActor, mRace, mClass, mFaction, mPcFaction, mCell; // Sound and text associated with this item std::string mSound, mResponse; From 2d4a6c0edfcb355e50a89fe54d21b213f9c754d1 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 5 Nov 2013 12:56:20 +0100 Subject: [PATCH 176/434] cleaned up ID handling in INFO record (including a ESMTool bug fix) --- apps/esmtool/esmtool.cpp | 2 ++ apps/opencs/model/world/infocollection.cpp | 2 +- apps/openmw/mwworld/esmstore.cpp | 2 ++ apps/openmw/mwworld/store.hpp | 2 +- components/esm/loadinfo.cpp | 1 - 5 files changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index a60e9f0e20..1be3264387 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -339,6 +339,8 @@ int load(Arguments& info) } std::string id = esm.getHNOString("NAME"); + if (id.empty()) + id = esm.getHNOString("INAM"); if(!quiet && interested) std::cout << "\nRecord: " << n.toString() diff --git a/apps/opencs/model/world/infocollection.cpp b/apps/opencs/model/world/infocollection.cpp index e21c3d4776..96d2f74173 100644 --- a/apps/opencs/model/world/infocollection.cpp +++ b/apps/opencs/model/world/infocollection.cpp @@ -35,7 +35,7 @@ void CSMWorld::InfoCollection::load (ESM::ESMReader& reader, bool base, const ES { /// \todo put records into proper order /// \todo adjust ID - std::string id = reader.getHNOString ("NAME"); + std::string id = reader.getHNOString ("INAM"); if (reader.isNextSub ("DELE")) { diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 7703f2d233..616ecc514d 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -70,8 +70,10 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) if (it == mStores.end()) { if (n.val == ESM::REC_INFO) { + std::string id = esm.getHNOString("INAM"); if (dialogue) { dialogue->mInfo.push_back(ESM::DialInfo()); + dialogue->mInfo.back().mId = id; dialogue->mInfo.back().load(esm); } else { std::cerr << "error: info record without dialog" << std::endl; diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 90452f6614..c25197319e 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -258,7 +258,7 @@ namespace MWWorld typename std::vector::iterator sharedIter = mShared.begin(); typename std::vector::iterator end = sharedIter + mStatic.size(); - while (sharedIter != mShared.end() && sharedIter != end) { + while (sharedIter != mShared.end() && sharedIter != end) { if((*sharedIter)->mId == item.mId) { mShared.erase(sharedIter); break; diff --git a/components/esm/loadinfo.cpp b/components/esm/loadinfo.cpp index be07e51282..5dc35db88f 100644 --- a/components/esm/loadinfo.cpp +++ b/components/esm/loadinfo.cpp @@ -8,7 +8,6 @@ namespace ESM void DialInfo::load(ESMReader &esm) { - mId = esm.getHNString("INAM"); mPrev = esm.getHNString("PNAM"); mNext = esm.getHNString("NNAM"); From c545b3682ac27f5c70e02ed66fc673c0a842df00 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 5 Nov 2013 12:57:55 +0100 Subject: [PATCH 177/434] compose info record IDs from actual record ID and parent topic ID (to make sure IDs are unique) --- apps/opencs/model/world/infocollection.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/opencs/model/world/infocollection.cpp b/apps/opencs/model/world/infocollection.cpp index 96d2f74173..1be186f760 100644 --- a/apps/opencs/model/world/infocollection.cpp +++ b/apps/opencs/model/world/infocollection.cpp @@ -34,8 +34,7 @@ void CSMWorld::InfoCollection::load (const Info& record, bool base) void CSMWorld::InfoCollection::load (ESM::ESMReader& reader, bool base, const ESM::Dialogue& dialogue) { /// \todo put records into proper order - /// \todo adjust ID - std::string id = reader.getHNOString ("INAM"); + std::string id = dialogue.mId + "#" + reader.getHNOString ("INAM"); if (reader.isNextSub ("DELE")) { From bf5529819d9c907b6fe28b8fdd72337c2fd7fbd2 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 5 Nov 2013 19:50:24 +0100 Subject: [PATCH 178/434] Added combo box widget and use it in options menu. Fixes to VBox / HBox. Added savegame dialog. --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwgui/mainmenu.cpp | 15 +++++ apps/openmw/mwgui/savegamedialog.cpp | 49 ++++++++++++++ apps/openmw/mwgui/savegamedialog.hpp | 37 +++++++++++ apps/openmw/mwgui/settingswindow.cpp | 42 ++---------- apps/openmw/mwgui/settingswindow.hpp | 8 +-- apps/openmw/mwgui/widgets.cpp | 40 +++++++++-- files/mygui/CMakeLists.txt | 1 + files/mygui/openmw_list.skin.xml | 13 ++++ files/mygui/openmw_resources.xml | 18 +++++ files/mygui/openmw_savegame_dialog.layout | 81 +++++++++++++++++++++++ files/mygui/openmw_settings_window.layout | 19 ++++-- 12 files changed, 274 insertions(+), 51 deletions(-) create mode 100644 apps/openmw/mwgui/savegamedialog.cpp create mode 100644 apps/openmw/mwgui/savegamedialog.hpp create mode 100644 files/mygui/openmw_savegame_dialog.layout diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index b367e2a1e7..f07bbab86b 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -34,7 +34,7 @@ add_openmw_dir (mwgui enchantingdialog trainingwindow travelwindow imagebutton exposedwindow cursor spellicons merchantrepair repair soulgemdialog companionwindow bookpage journalviewmodel journalbooks keywordsearch itemmodel containeritemmodel inventoryitemmodel sortfilteritemmodel itemview - tradeitemmodel companionitemmodel pickpocketitemmodel fontloader controllers + tradeitemmodel companionitemmodel pickpocketitemmodel fontloader controllers savegamedialog ) add_openmw_dir (mwdialogue diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp index 2c42dc210f..fa7ed2aceb 100644 --- a/apps/openmw/mwgui/mainmenu.cpp +++ b/apps/openmw/mwgui/mainmenu.cpp @@ -9,6 +9,8 @@ #include "../mwbase/journal.hpp" #include "../mwbase/dialoguemanager.hpp" +#include "savegamedialog.hpp" + namespace MWGui { @@ -85,6 +87,19 @@ namespace MWGui MWBase::Environment::get().getDialogueManager()->clear(); MWBase::Environment::get().getJournal()->clear(); } + + else if (sender == mButtons["loadgame"]) + { + MWGui::SaveGameDialog* dialog = new MWGui::SaveGameDialog(); + dialog->setLoadOrSave(true); + dialog->setVisible(true); + } + else if (sender == mButtons["savegame"]) + { + MWGui::SaveGameDialog* dialog = new MWGui::SaveGameDialog(); + dialog->setLoadOrSave(false); + dialog->setVisible(true); + } } } diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp new file mode 100644 index 0000000000..a1acd35884 --- /dev/null +++ b/apps/openmw/mwgui/savegamedialog.cpp @@ -0,0 +1,49 @@ +#include "savegamedialog.hpp" +#include "widgets.hpp" + + +namespace MWGui +{ + + SaveGameDialog::SaveGameDialog() + : WindowModal("openmw_savegame_dialog.layout") + { + getWidget(mScreenshot, "Screenshot"); + getWidget(mCharacterSelection, "SelectCharacter"); + getWidget(mInfoText, "InfoText"); + getWidget(mOkButton, "OkButton"); + getWidget(mCancelButton, "CancelButton"); + getWidget(mSaveList, "SaveList"); + getWidget(mSaveNameEdit, "SaveNameEdit"); + getWidget(mSpacer, "Spacer"); + mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onOkButtonClicked); + mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onCancelButtonClicked); + + } + + void SaveGameDialog::open() + { + center(); + } + + void SaveGameDialog::setLoadOrSave(bool load) + { + mSaveNameEdit->setVisible(!load); + mCharacterSelection->setUserString("Hidden", load ? "false" : "true"); + mCharacterSelection->setVisible(load); + mSpacer->setUserString("Hidden", load ? "false" : "true"); + + center(); + } + + void SaveGameDialog::onCancelButtonClicked(MyGUI::Widget *sender) + { + setVisible(false); + } + + void SaveGameDialog::onOkButtonClicked(MyGUI::Widget *sender) + { + setVisible(false); + } + +} diff --git a/apps/openmw/mwgui/savegamedialog.hpp b/apps/openmw/mwgui/savegamedialog.hpp new file mode 100644 index 0000000000..1a3178ef30 --- /dev/null +++ b/apps/openmw/mwgui/savegamedialog.hpp @@ -0,0 +1,37 @@ +#ifndef OPENMW_MWGUI_SAVEGAMEDIALOG_H +#define OPENMW_MWGUI_SAVEGAMEDIALOG_H + +#include "windowbase.hpp" + +namespace MWGui +{ + + class SaveGameDialog : public MWGui::WindowModal + { + public: + SaveGameDialog(); + + virtual void open(); + + void setLoadOrSave(bool load); + + void onCancelButtonClicked (MyGUI::Widget* sender); + void onOkButtonClicked (MyGUI::Widget* sender); + + + private: + MyGUI::ImageBox* mScreenshot; + + MyGUI::ComboBox* mCharacterSelection; + MyGUI::EditBox* mInfoText; + MyGUI::Button* mOkButton; + MyGUI::Button* mCancelButton; + MyGUI::ListBox* mSaveList; + MyGUI::EditBox* mSaveNameEdit; + MyGUI::Widget* mSpacer; + + }; + +} + +#endif diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 923b9d01d6..1969c56518 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -31,9 +31,7 @@ namespace std::string textureFilteringToStr(const std::string& val) { - if (val == "none") - return "None"; - else if (val == "anisotropic") + if (val == "anisotropic") return "Anisotropic"; else if (val == "bilinear") return "Bilinear"; @@ -145,7 +143,7 @@ namespace MWGui mReflectObjectsButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mReflectTerrainButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mReflectActorsButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); - mTextureFilteringButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onTextureFilteringToggled); + mTextureFilteringButton->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onTextureFilteringChanged); mVSyncButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mFPSButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onFpsToggled); mMenuTransparencySlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); @@ -157,7 +155,7 @@ namespace MWGui mShadowsEnabledButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mShadowsLargeDistance->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); - mShadowsTextureSize->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onShadowTextureSize); + mShadowsTextureSize->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onShadowTextureSizeChanged); mActorShadows->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mStaticsShadows->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mMiscShadows->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); @@ -297,22 +295,9 @@ namespace MWGui mResolutionList->setIndexSelected(MyGUI::ITEM_NONE); } - void SettingsWindow::onShadowTextureSize(MyGUI::Widget* _sender) + void SettingsWindow::onShadowTextureSizeChanged(MyGUI::ComboBox *_sender, size_t pos) { - std::string size = mShadowsTextureSize->getCaption(); - - if (size == "512") - size = "1024"; - else if (size == "1024") - size = "2048"; - else if (size == "2048") - size = "4096"; - else - size = "512"; - - mShadowsTextureSize->setCaption(size); - - Settings::Manager::setString("texture size", "Shadows", size); + Settings::Manager::setString("texture size", "Shadows", _sender->getItemNameAt(pos)); apply(); } @@ -482,22 +467,9 @@ namespace MWGui apply(); } - void SettingsWindow::onTextureFilteringToggled(MyGUI::Widget* _sender) + void SettingsWindow::onTextureFilteringChanged(MyGUI::ComboBox* _sender, size_t pos) { - std::string current = Settings::Manager::getString("texture filtering", "General"); - std::string next; - if (current == "none") - next = "bilinear"; - else if (current == "bilinear") - next = "trilinear"; - else if (current == "trilinear") - next = "anisotropic"; - else - next = "none"; - - mTextureFilteringButton->setCaption(textureFilteringToStr(next)); - - Settings::Manager::setString("texture filtering", "General", next); + Settings::Manager::setString("texture filtering", "General", Misc::StringUtils::lowerCase(_sender->getItemNameAt(pos))); apply(); } diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index a585bda7e1..c81a86ab0e 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -42,7 +42,7 @@ namespace MWGui MyGUI::ScrollBar* mViewDistanceSlider; MyGUI::ScrollBar* mFOVSlider; MyGUI::ScrollBar* mAnisotropySlider; - MyGUI::Button* mTextureFilteringButton; + MyGUI::ComboBox* mTextureFilteringButton; MyGUI::TextBox* mAnisotropyLabel; MyGUI::Widget* mAnisotropyBox; MyGUI::Button* mWaterShaderButton; @@ -55,7 +55,7 @@ namespace MWGui MyGUI::Button* mShadowsEnabledButton; MyGUI::Button* mShadowsLargeDistance; - MyGUI::Button* mShadowsTextureSize; + MyGUI::ComboBox* mShadowsTextureSize; MyGUI::Button* mActorShadows; MyGUI::Button* mStaticsShadows; MyGUI::Button* mMiscShadows; @@ -76,7 +76,7 @@ namespace MWGui void onOkButtonClicked(MyGUI::Widget* _sender); void onFpsToggled(MyGUI::Widget* _sender); - void onTextureFilteringToggled(MyGUI::Widget* _sender); + void onTextureFilteringChanged(MyGUI::ComboBox* _sender, size_t pos); void onSliderChangePosition(MyGUI::ScrollBar* scroller, size_t pos); void onButtonToggled(MyGUI::Widget* _sender); void onResolutionSelected(MyGUI::ListBox* _sender, size_t index); @@ -85,7 +85,7 @@ namespace MWGui void onShadersToggled(MyGUI::Widget* _sender); void onShaderModeToggled(MyGUI::Widget* _sender); - void onShadowTextureSize(MyGUI::Widget* _sender); + void onShadowTextureSizeChanged(MyGUI::ComboBox* _sender, size_t pos); void onRebindAction(MyGUI::Widget* _sender); void onInputTabMouseWheel(MyGUI::Widget* _sender, int _rel); diff --git a/apps/openmw/mwgui/widgets.cpp b/apps/openmw/mwgui/widgets.cpp index 3fc3187e80..c37ae15fa1 100644 --- a/apps/openmw/mwgui/widgets.cpp +++ b/apps/openmw/mwgui/widgets.cpp @@ -689,22 +689,26 @@ namespace MWGui int total_width = 0; int total_height = 0; std::vector< std::pair > sizes; + sizes.resize(count); for (unsigned int i = 0; i < count; ++i) { MyGUI::Widget* w = getChildAt(i); bool hstretch = w->getUserString ("HStretch") == "true"; + bool hidden = w->getUserString("Hidden") == "true"; + if (hidden) + continue; h_stretched_count += hstretch; AutoSizedWidget* aw = dynamic_cast(w); if (aw) { - sizes.push_back(std::make_pair(aw->getRequestedSize (), hstretch)); + sizes[i] = std::make_pair(aw->getRequestedSize (), hstretch); total_width += aw->getRequestedSize ().width; total_height = std::max(total_height, aw->getRequestedSize ().height); } else { - sizes.push_back (std::make_pair(w->getSize(), hstretch)); + sizes[i] = std::make_pair(w->getSize(), hstretch); total_width += w->getSize().width; if (!(w->getUserString("VStretch") == "true")) total_height = std::max(total_height, w->getSize().height); @@ -729,8 +733,13 @@ namespace MWGui MyGUI::Widget* w = getChildAt(i); + bool hidden = w->getUserString("Hidden") == "true"; + if (hidden) + continue; + bool vstretch = w->getUserString ("VStretch") == "true"; - int height = vstretch ? total_height : sizes[i].first.height; + int max_height = getSize().height - mPadding*2; + int height = vstretch ? max_height : sizes[i].first.height; MyGUI::IntCoord widgetCoord; widgetCoord.left = curX; @@ -774,6 +783,10 @@ namespace MWGui MyGUI::IntSize size(0,0); for (unsigned int i = 0; i < getChildCount (); ++i) { + bool hidden = getChildAt(i)->getUserString("Hidden") == "true"; + if (hidden) + continue; + AutoSizedWidget* w = dynamic_cast(getChildAt(i)); if (w) { @@ -810,21 +823,27 @@ namespace MWGui int total_height = 0; int total_width = 0; std::vector< std::pair > sizes; + sizes.resize(count); for (unsigned int i = 0; i < count; ++i) { MyGUI::Widget* w = getChildAt(i); + + bool hidden = w->getUserString("Hidden") == "true"; + if (hidden) + continue; + bool vstretch = w->getUserString ("VStretch") == "true"; v_stretched_count += vstretch; AutoSizedWidget* aw = dynamic_cast(w); if (aw) { - sizes.push_back(std::make_pair(aw->getRequestedSize (), vstretch)); + sizes[i] = std::make_pair(aw->getRequestedSize (), vstretch); total_height += aw->getRequestedSize ().height; total_width = std::max(total_width, aw->getRequestedSize ().width); } else { - sizes.push_back (std::make_pair(w->getSize(), vstretch)); + sizes[i] = std::make_pair(w->getSize(), vstretch); total_height += w->getSize().height; if (!(w->getUserString("HStretch") == "true")) @@ -850,8 +869,13 @@ namespace MWGui MyGUI::Widget* w = getChildAt(i); + bool hidden = w->getUserString("Hidden") == "true"; + if (hidden) + continue; + bool hstretch = w->getUserString ("HStretch") == "true"; - int width = hstretch ? total_width : sizes[i].first.width; + int maxWidth = getSize().width - mPadding*2; + int width = hstretch ? maxWidth : sizes[i].first.width; MyGUI::IntCoord widgetCoord; widgetCoord.top = curY; @@ -890,6 +914,10 @@ namespace MWGui MyGUI::IntSize size(0,0); for (unsigned int i = 0; i < getChildCount (); ++i) { + bool hidden = getChildAt(i)->getUserString("Hidden") == "true"; + if (hidden) + continue; + AutoSizedWidget* w = dynamic_cast(getChildAt(i)); if (w) { diff --git a/files/mygui/CMakeLists.txt b/files/mygui/CMakeLists.txt index 1ec1e08cb5..3329c01f92 100644 --- a/files/mygui/CMakeLists.txt +++ b/files/mygui/CMakeLists.txt @@ -81,6 +81,7 @@ set(MYGUI_FILES openmw_merchantrepair.layout openmw_repair.layout openmw_companion_window.layout + openmw_savegame_dialog.layout smallbars.png DejaVuLGCSansMono.ttf markers.png diff --git a/files/mygui/openmw_list.skin.xml b/files/mygui/openmw_list.skin.xml index 02c11c3549..7972527acc 100644 --- a/files/mygui/openmw_list.skin.xml +++ b/files/mygui/openmw_list.skin.xml @@ -126,6 +126,19 @@ + + + + + + + + + + + + + diff --git a/files/mygui/openmw_resources.xml b/files/mygui/openmw_resources.xml index 2c3908a1b9..3901ab8252 100644 --- a/files/mygui/openmw_resources.xml +++ b/files/mygui/openmw_resources.xml @@ -301,4 +301,22 @@ + + + + + + + + + + + + + + + + + + diff --git a/files/mygui/openmw_savegame_dialog.layout b/files/mygui/openmw_savegame_dialog.layout new file mode 100644 index 0000000000..18de6a2399 --- /dev/null +++ b/files/mygui/openmw_savegame_dialog.layout @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index ebfaf678a7..e6002b51de 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -216,7 +216,11 @@ - + + + + + @@ -308,9 +312,9 @@ - + - + @@ -336,9 +340,14 @@ - + + + + + + - + From 9e2b1942fc119d05b6f6a9e7e1638f44d663a03d Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 5 Nov 2013 22:50:53 +0100 Subject: [PATCH 179/434] Fix journal for not installed tribunal (Options button should become Topics). Don't log an error when optional journal buttons (Tribunal) are not found. --- apps/openmw/mwgui/imagebutton.cpp | 5 +++-- apps/openmw/mwgui/imagebutton.hpp | 2 +- apps/openmw/mwgui/journalwindow.cpp | 27 ++++++++++++++++++++------- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwgui/imagebutton.cpp b/apps/openmw/mwgui/imagebutton.cpp index 98f05373ba..f2565f5c00 100644 --- a/apps/openmw/mwgui/imagebutton.cpp +++ b/apps/openmw/mwgui/imagebutton.cpp @@ -42,12 +42,13 @@ namespace MWGui ImageBox::onMouseButtonPressed(_left, _top, _id); } - MyGUI::IntSize ImageButton::getRequestedSize() + MyGUI::IntSize ImageButton::getRequestedSize(bool logError) { Ogre::TexturePtr texture = Ogre::TextureManager::getSingleton().getByName(mImageNormal); if (texture.isNull()) { - std::cerr << "ImageButton: can't find " << mImageNormal << std::endl; + if (logError) + std::cerr << "ImageButton: can't find " << mImageNormal << std::endl; return MyGUI::IntSize(0,0); } return MyGUI::IntSize (texture->getWidth(), texture->getHeight()); diff --git a/apps/openmw/mwgui/imagebutton.hpp b/apps/openmw/mwgui/imagebutton.hpp index f531e22469..f4191a3a54 100644 --- a/apps/openmw/mwgui/imagebutton.hpp +++ b/apps/openmw/mwgui/imagebutton.hpp @@ -14,7 +14,7 @@ namespace MWGui MYGUI_RTTI_DERIVED(ImageButton) public: - MyGUI::IntSize getRequestedSize(); + MyGUI::IntSize getRequestedSize(bool logError = true); protected: virtual void setPropertyOverride(const std::string& _key, const std::string& _value); diff --git a/apps/openmw/mwgui/journalwindow.cpp b/apps/openmw/mwgui/journalwindow.cpp index ab8dc1584a..f3c9e9c73a 100644 --- a/apps/openmw/mwgui/journalwindow.cpp +++ b/apps/openmw/mwgui/journalwindow.cpp @@ -137,15 +137,28 @@ namespace getPage (QuestsPage)->adviseLinkClicked (callback); } - adjustButton(OptionsBTN); + adjustButton(OptionsBTN, true); adjustButton(PrevPageBTN); adjustButton(NextPageBTN); adjustButton(CloseBTN); adjustButton(CancelBTN); - adjustButton(ShowAllBTN); - adjustButton(ShowActiveBTN); + adjustButton(ShowAllBTN, true); + adjustButton(ShowActiveBTN, true); adjustButton(JournalBTN); + MWGui::ImageButton* optionsButton = getWidget(OptionsBTN); + if (optionsButton->getWidth() == 0) + { + // If tribunal is not installed (-> no options button), we still want the Topics button available, + // so place it where the options button would have been + MWGui::ImageButton* topicsButton = getWidget(TopicsBTN); + topicsButton->detachFromWidget(); + topicsButton->attachToWidget(optionsButton->getParent()); + topicsButton->setPosition(optionsButton->getPosition()); + topicsButton->eventMouseButtonClick.clear(); + topicsButton->eventMouseButtonClick += MyGUI::newDelegate(this, &JournalWindowImpl::notifyOptions); + } + MWGui::ImageButton* nextButton = getWidget(NextPageBTN); if (nextButton->getSize().width == 64) { @@ -155,7 +168,7 @@ namespace } adjustButton(TopicsBTN); - adjustButton(QuestsBTN); + adjustButton(QuestsBTN, true); int width = getWidget(TopicsBTN)->getSize().width + getWidget(QuestsBTN)->getSize().width; int topicsWidth = getWidget(TopicsBTN)->getSize().width; int pageWidth = getWidget(RightBookPage)->getSize().width; @@ -167,12 +180,12 @@ namespace mAllQuests = false; } - void adjustButton (char const * name) + void adjustButton (char const * name, bool optional = false) { MWGui::ImageButton* button = getWidget(name); - MyGUI::IntSize diff = button->getSize() - button->getRequestedSize(); - button->setSize(button->getRequestedSize()); + MyGUI::IntSize diff = button->getSize() - button->getRequestedSize(!optional); + button->setSize(button->getRequestedSize(!optional)); if (button->getAlign().isRight()) button->setPosition(button->getPosition() + MyGUI::IntPoint(diff.width,0)); From 2f5ad4c16f0752bab3af669535953a1cdbd90da2 Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Wed, 6 Nov 2013 21:10:00 +0100 Subject: [PATCH 180/434] Small changes. --- manual/opencs/filters.tex | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/manual/opencs/filters.tex b/manual/opencs/filters.tex index f69f6d7f1b..2c5e0939e8 100644 --- a/manual/opencs/filters.tex +++ b/manual/opencs/filters.tex @@ -3,7 +3,7 @@ Filters are the key element of OpenCS use cases by allowing rapid and easy access to the searched records presented in all tables. Therefore: in order to use this application fully effective you should make sure that all concepts and instructions written in the this section of the manual are perfectly clear to you.\\ Don't be afraid though, filters are fairly intuitive and easy to use. -\subsection{Used Terms} +\subsubsection{Used Terms} \begin{description} \item[Filter] is generally speaking a tool able to ``Filter'' (that is: select some elements, while discarding others) according to the some criteria. In case of OpenCS: records are being filtered according to the criteria of user choice. Criteria are written down in language with simple syntax. @@ -15,10 +15,10 @@ Don't be afraid though, filters are fairly intuitive and easy to use. \item[nullary] is expression that does not accepts other expressions. It accepts arguments specified later. \end{description} -\subsection{Basics} +\subsubsection{Basics} In fact you don't need to learn everything about filters in order to use them. In fact all you need to know to achieve decent productivity with OpenCS is inside basics section. -\subsection{Interface} +\subsubsection{Interface} Above each table there is a field that is used to enter filter: either predefined by the OpenMW developers or made by you, the user. You probably noticed it before. However there is also completely new element, although using familiar table layout. Go to the application menu view, and click filters. You should see set of default filters, made by the OpenMW team in the table with the following columns: filter, description and modified. \begin{description} @@ -29,7 +29,7 @@ Above each table there is a field that is used to enter filter: either predefine \end{description} So let's learn how to actually use those to speed up your work. -\subsection{Using predefined filters} +\subsubsection{Using predefined filters} Using those filters is quite easy and involves typing inside the filter field above the table. For instance, try to open referencables table and type in the filters field the following: ``project::weapons''. As soon as you complete the text, table will magicly alter and will show only the weapons. As you could noticed project::weapons is nothing else than a ID of one of the predefined filters. That's it: in order to use the filter inside the table you simply type it's name inside the filter field.\\ To make life easier filter IDs follow simple convention. @@ -83,7 +83,7 @@ As you would imagine the range can be specified as including a border value, or \end{itemize} \subsubsection{Logical expressions} -This subsection takes care of two remaining groups of expressions: binary and unary. The only unary expression present in the OpenCS is logical not, while the remaining binary expressions are: or, and. This clearly makes theme from user point of view belonging to the same group of logical expressions. +This subsection takes care of two remaining groups of expressions: binary and unary. The only unary expression present in the OpenCS is logical not, while the remaining binary expressions are: or, and. This clearly makes them (from the user point of view) belonging to the same group of logical expressions. \paragraph{not -- not expression()} Sometimes you may be in need of reversing the output of the expression. This is where not comes in handy. Adding not before expression will revert it: if expression was returning true, it will return false; if it was returning false, it will return true. Brackets are not needed: not will revert only the first expression following it.\\ From 470616ce9274031bfbb26fec5e06482e030403b9 Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Wed, 6 Nov 2013 22:01:37 +0100 Subject: [PATCH 181/434] Added windows.tex. Does not contain a lot at this point. --- manual/opencs/main.tex | 1 + manual/opencs/windows.tex | 12 ++++++++++++ 2 files changed, 13 insertions(+) create mode 100644 manual/opencs/windows.tex diff --git a/manual/opencs/main.tex b/manual/opencs/main.tex index 64f9baf5c9..603e2d9ba3 100644 --- a/manual/opencs/main.tex +++ b/manual/opencs/main.tex @@ -8,6 +8,7 @@ \title{OpenCS User Manual} \maketitle \tableofcontents{} +\input{windows} \input{tables} \input{filters} \end{document} diff --git a/manual/opencs/windows.tex b/manual/opencs/windows.tex new file mode 100644 index 0000000000..ad1d2f951c --- /dev/null +++ b/manual/opencs/windows.tex @@ -0,0 +1,12 @@ +\section{Windows} +\subsection{Introduction} +This section describes the multiple windows interface of the OpenCS editor. This design principle was chosen in order to extend the flexibility of the editor, especially on the multiple screens setups and on environments providing advanced windows management features, like; for instance; multiple desktops found commonly on many open source desktop environments. However, it is enough to have a single large screen to see the advantages of this concept.\\ +OpenCS windows interface is easy to describe and understand. In fact We decided to minimize use of many windows concepts applied commonly in various applications. For instance dialog windows are really hard to find in the OpenCS. You are free to try, though.\\ +Because of this, and the fact that we expect that user is familiar with other applications using windows this section is mostly focused on practical ways of organizing work with the OpenCS. + +\subsection{Basics} +After starting Open{CS} and choosing content files to use a editor window should show up. It probably does not look surprising: there is a menubar at the top, and there is a large empty area. That's it: a brand new Open{CS} window contains only menubar and statusbar. In order to make it a little bit more useful you probably want to enable some window widgets. You are free to do so, just try to explore the menubar. \\ +You probably founded out the way to enable and disable some interesting tables, but those will be described later. For now, let's just focus on the windows itself. + +\paragraph{Creating new windows} +is easy! Just visit view menu, and use the ``New View'' item. Suddenly, out of the blue a new window will show up. As you would expect, it is also blank, and you are free to add any of the Open{CS} widgets. \ No newline at end of file From e94da61ff03f77897a7a9100864d6c5d165735a0 Mon Sep 17 00:00:00 2001 From: Jordan Ayers Date: Tue, 5 Nov 2013 19:38:45 -0600 Subject: [PATCH 182/434] Chargen cleanup. Move common gui state pop out of if checks. Move health update before gui state push (used as input for some windows). --- apps/openmw/mwgui/charactercreation.cpp | 43 +++++++++---------------- 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/apps/openmw/mwgui/charactercreation.cpp b/apps/openmw/mwgui/charactercreation.cpp index 816f42e3d1..9b742742d8 100644 --- a/apps/openmw/mwgui/charactercreation.cpp +++ b/apps/openmw/mwgui/charactercreation.cpp @@ -332,24 +332,22 @@ namespace MWGui mPickClassDialog = 0; } + updatePlayerHealth(); + //TODO This bit gets repeated a few times; wrap it in a function + MWBase::Environment::get().getWindowManager()->popGuiMode(); if (mCreationStage == CSE_ReviewNext) { - MWBase::Environment::get().getWindowManager()->popGuiMode(); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Review); } else if (mCreationStage >= CSE_ClassChosen) { - MWBase::Environment::get().getWindowManager()->popGuiMode(); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Birth); } else { mCreationStage = CSE_ClassChosen; - MWBase::Environment::get().getWindowManager()->popGuiMode(); } - - updatePlayerHealth(); } void CharacterCreation::onPickClassDialogBack() @@ -403,20 +401,18 @@ namespace MWGui mNameDialog = 0; } + MWBase::Environment::get().getWindowManager()->popGuiMode(); if (mCreationStage == CSE_ReviewNext) { - MWBase::Environment::get().getWindowManager()->popGuiMode(); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Review); } else if (mCreationStage >= CSE_NameChosen) { - MWBase::Environment::get().getWindowManager()->popGuiMode(); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Race); } else { mCreationStage = CSE_NameChosen; - MWBase::Environment::get().getWindowManager()->popGuiMode(); } } @@ -462,23 +458,21 @@ namespace MWGui mRaceDialog = 0; } + updatePlayerHealth(); + + MWBase::Environment::get().getWindowManager()->popGuiMode(); if (mCreationStage == CSE_ReviewNext) { - MWBase::Environment::get().getWindowManager()->popGuiMode(); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Review); } else if (mCreationStage >= CSE_RaceChosen) { - MWBase::Environment::get().getWindowManager()->popGuiMode(); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Class); } else { mCreationStage = CSE_RaceChosen; - MWBase::Environment::get().getWindowManager()->popGuiMode(); } - - updatePlayerHealth(); } void CharacterCreation::onBirthSignDialogDone(WindowBase* parWindow) @@ -492,18 +486,17 @@ namespace MWGui mBirthSignDialog = 0; } + updatePlayerHealth(); + + MWBase::Environment::get().getWindowManager()->popGuiMode(); if (mCreationStage >= CSE_BirthSignChosen) { - MWBase::Environment::get().getWindowManager()->popGuiMode(); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Review); } else { mCreationStage = CSE_BirthSignChosen; - MWBase::Environment::get().getWindowManager()->popGuiMode(); } - - updatePlayerHealth(); } void CharacterCreation::onBirthSignDialogBack() @@ -552,23 +545,21 @@ namespace MWGui mCreateClassDialog = 0; } + updatePlayerHealth(); + + MWBase::Environment::get().getWindowManager()->popGuiMode(); if (mCreationStage == CSE_ReviewNext) { - MWBase::Environment::get().getWindowManager()->popGuiMode(); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Review); } else if (mCreationStage >= CSE_ClassChosen) { - MWBase::Environment::get().getWindowManager()->popGuiMode(); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Birth); } else { mCreationStage = CSE_ClassChosen; - MWBase::Environment::get().getWindowManager()->popGuiMode(); } - - updatePlayerHealth(); } void CharacterCreation::onCreateClassDialogBack() @@ -722,23 +713,21 @@ namespace MWGui mPlayerClass = *klass; MWBase::Environment::get().getWindowManager()->setPlayerClass(mPlayerClass); + updatePlayerHealth(); + + MWBase::Environment::get().getWindowManager()->popGuiMode(); if (mCreationStage == CSE_ReviewNext) { - MWBase::Environment::get().getWindowManager()->popGuiMode(); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Review); } else if (mCreationStage >= CSE_ClassChosen) { - MWBase::Environment::get().getWindowManager()->popGuiMode(); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Birth); } else { mCreationStage = CSE_ClassChosen; - MWBase::Environment::get().getWindowManager()->popGuiMode(); } - - updatePlayerHealth(); } CharacterCreation::~CharacterCreation() From d48cc27a8938279c22aa6408a9d4a245b532c9bf Mon Sep 17 00:00:00 2001 From: Jordan Ayers Date: Tue, 5 Nov 2013 19:39:43 -0600 Subject: [PATCH 183/434] Chargen fix: Back sequence control. Related to bug #894. Eliminate double windows when using 'Back' from the review screen. Force consistent back behavior (handling was changing after a individual change button had been used). --- apps/openmw/mwgui/charactercreation.cpp | 2 ++ apps/openmw/mwgui/charactercreation.hpp | 1 + 2 files changed, 3 insertions(+) diff --git a/apps/openmw/mwgui/charactercreation.cpp b/apps/openmw/mwgui/charactercreation.cpp index 9b742742d8..eb087ea07b 100644 --- a/apps/openmw/mwgui/charactercreation.cpp +++ b/apps/openmw/mwgui/charactercreation.cpp @@ -285,7 +285,9 @@ namespace MWGui { MWBase::Environment::get().getWindowManager()->removeDialog(mReviewDialog); mReviewDialog = 0; + mCreationStage = CSE_ReviewBack; + MWBase::Environment::get().getWindowManager()->popGuiMode(); MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Birth); } diff --git a/apps/openmw/mwgui/charactercreation.hpp b/apps/openmw/mwgui/charactercreation.hpp index bd88266776..bb2a69a7a3 100644 --- a/apps/openmw/mwgui/charactercreation.hpp +++ b/apps/openmw/mwgui/charactercreation.hpp @@ -104,6 +104,7 @@ namespace MWGui CSE_RaceChosen, CSE_ClassChosen, CSE_BirthSignChosen, + CSE_ReviewBack, CSE_ReviewNext }; From 3fbf918751c0e6ea5f945069a3099e4df388421d Mon Sep 17 00:00:00 2001 From: Jordan Ayers Date: Tue, 5 Nov 2013 19:42:55 -0600 Subject: [PATCH 184/434] Chargen Review Dialog: Init fix. Load the starting Health/Magicka/Fatigue from the player stats when creating the Review Dialog, and remove the extra copy of these stats. In some cases, the old stat values were never updated from 0/0. --- apps/openmw/mwgui/charactercreation.cpp | 26 ++++++++----------------- apps/openmw/mwgui/charactercreation.hpp | 9 --------- apps/openmw/mwgui/windowmanagerimp.cpp | 5 +---- 3 files changed, 9 insertions(+), 31 deletions(-) diff --git a/apps/openmw/mwgui/charactercreation.cpp b/apps/openmw/mwgui/charactercreation.cpp index eb087ea07b..b829f219d7 100644 --- a/apps/openmw/mwgui/charactercreation.cpp +++ b/apps/openmw/mwgui/charactercreation.cpp @@ -219,9 +219,14 @@ namespace MWGui mReviewDialog->setClass(mPlayerClass); mReviewDialog->setBirthSign(mPlayerBirthSignId); - mReviewDialog->setHealth(mPlayerHealth); - mReviewDialog->setMagicka(mPlayerMagicka); - mReviewDialog->setFatigue(mPlayerFatigue); + { + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWMechanics::CreatureStats stats = MWWorld::Class::get(player).getCreatureStats(player); + + mReviewDialog->setHealth ( stats.getHealth() ); + mReviewDialog->setMagicka( stats.getMagicka() ); + mReviewDialog->setFatigue( stats.getFatigue() ); + } { std::map > attributes = MWBase::Environment::get().getWindowManager()->getPlayerAttributeValues(); @@ -258,21 +263,6 @@ namespace MWGui mRaceDialog->doRenderUpdate(); } - void CharacterCreation::setPlayerHealth (const MWMechanics::DynamicStat& value) - { - mPlayerHealth = value; - } - - void CharacterCreation::setPlayerMagicka (const MWMechanics::DynamicStat& value) - { - mPlayerMagicka = value; - } - - void CharacterCreation::setPlayerFatigue (const MWMechanics::DynamicStat& value) - { - mPlayerFatigue = value; - } - void CharacterCreation::onReviewDialogDone(WindowBase* parWindow) { MWBase::Environment::get().getWindowManager()->removeDialog(mReviewDialog); diff --git a/apps/openmw/mwgui/charactercreation.hpp b/apps/openmw/mwgui/charactercreation.hpp index bb2a69a7a3..b80aaae41c 100644 --- a/apps/openmw/mwgui/charactercreation.hpp +++ b/apps/openmw/mwgui/charactercreation.hpp @@ -31,12 +31,6 @@ namespace MWGui //Show a dialog void spawnDialog(const char id); - void setPlayerHealth (const MWMechanics::DynamicStat& value); - - void setPlayerMagicka (const MWMechanics::DynamicStat& value); - - void setPlayerFatigue (const MWMechanics::DynamicStat& value); - void setValue (const std::string& id, const MWMechanics::Stat& value); void setValue (const std::string& id, const MWMechanics::DynamicStat& value); void setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::Stat& value); @@ -60,9 +54,6 @@ namespace MWGui std::string mPlayerRaceId; std::string mPlayerBirthSignId; ESM::Class mPlayerClass; - MWMechanics::DynamicStat mPlayerHealth; - MWMechanics::DynamicStat mPlayerMagicka; - MWMechanics::DynamicStat mPlayerFatigue; //Class generation vars unsigned mGenerateClassStep; // Keeps track of current step in Generate Class dialog diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 4b4d2dfd19..76ae65eb9b 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -581,17 +581,14 @@ namespace MWGui if (id == "HBar") { mPlayerHealth = value; - mCharGen->setPlayerHealth (value); } else if (id == "MBar") { mPlayerMagicka = value; - mCharGen->setPlayerMagicka (value); } else if (id == "FBar") { mPlayerFatigue = value; - mCharGen->setPlayerFatigue (value); } } @@ -599,7 +596,7 @@ namespace MWGui MWMechanics::DynamicStat WindowManager::getValue(const std::string& id) { if(id == "HBar") - return layerHealth; + return mPlayerHealth; else if (id == "MBar") return mPlayerMagicka; else if (id == "FBar") From 36efe9605f1e2ac13db6f870cab384e83edfdf76 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Thu, 7 Nov 2013 23:05:45 +0100 Subject: [PATCH 185/434] necessary dpkg rules to get opencs building and packaged on dpkg systems --- CMakeLists.txt | 2 +- apps/opencs/CMakeLists.txt | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4d45264aed..efa2f47642 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -430,7 +430,7 @@ IF(NOT WIN32 AND NOT APPLE) Data files from the original game is required to run it.") SET(CPACK_DEBIAN_PACKAGE_NAME "openmw") SET(CPACK_DEBIAN_PACKAGE_VERSION "${VERSION_STRING}") - SET(CPACK_PACKAGE_EXECUTABLES "openmw;OpenMW bsatool;Bsatool esmtool;Esmtool omwlauncher;OMWLauncher mwiniimporter;MWiniImporter") + SET(CPACK_PACKAGE_EXECUTABLES "openmw;OpenMW opencs;OpenCS bsatool;Bsatool esmtool;Esmtool omwlauncher;OMWLauncher mwiniimporter;MWiniImporter") SET(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.11.2), libfreetype6 (>= 2.2.1), libgcc1 (>= 1:4.1.1), libmpg123-0 (>= 1.12.1), libopenal1 (>= 1:1.12.854), libsndfile1 (>= 1.0.23), libstdc++6 (>= 4.4.5), libuuid1 (>= 2.17.2), libqtgui4 (>= 4.7.0)") SET(CPACK_DEBIAN_PACKAGE_SECTION "Games") diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index d317331e51..7498044ab5 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -160,3 +160,8 @@ target_link_libraries(opencs ${QT_LIBRARIES} components ) + +if(DPKG_PROGRAM) + INSTALL(TARGETS opencs RUNTIME DESTINATION games COMPONENT opencs) +endif() + From 6b931f566dedff256b1b429e5d9c2b8b0809acaa Mon Sep 17 00:00:00 2001 From: eroen Date: Fri, 8 Nov 2013 04:24:36 +0100 Subject: [PATCH 186/434] Stop installing "Daedric Font License.txt" It was removed in 3a827d9c12 --- CMakeLists.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4d45264aed..9e6f4eb9c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -382,7 +382,6 @@ IF(NOT WIN32 AND NOT APPLE) # Install licenses INSTALL(FILES "DejaVu Font License.txt" DESTINATION "${LICDIR}" ) - INSTALL(FILES "Daedric Font License.txt" DESTINATION "${LICDIR}" ) INSTALL(FILES "OFL.txt" DESTINATION "${LICDIR}" ) INSTALL(FILES "extern/shiny/License.txt" DESTINATION "${LICDIR}" RENAME "Shiny License.txt" ) ENDIF (DPKG_PROGRAM) @@ -457,7 +456,6 @@ if(WIN32) "${OpenMW_SOURCE_DIR}/GPL3.txt" "${OpenMW_SOURCE_DIR}/OFL.txt" "${OpenMW_SOURCE_DIR}/DejaVu Font License.txt" - "${OpenMW_SOURCE_DIR}/Daedric Font License.txt" "${OpenMW_BINARY_DIR}/settings-default.cfg" "${OpenMW_BINARY_DIR}/transparency-overrides.cfg" "${OpenMW_BINARY_DIR}/Release/openmw.exe" From e885c502a1c7742d4fff43afa94121dad91d4418 Mon Sep 17 00:00:00 2001 From: eroen Date: Fri, 8 Nov 2013 04:24:36 +0100 Subject: [PATCH 187/434] Stop installing "Daedric Font License.txt" It was removed in 3a827d9c12 --- CMakeLists.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7f31a5e533..fcc63e1b9e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -383,7 +383,6 @@ IF(NOT WIN32 AND NOT APPLE) # Install licenses INSTALL(FILES "DejaVu Font License.txt" DESTINATION "${LICDIR}" ) - INSTALL(FILES "Daedric Font License.txt" DESTINATION "${LICDIR}" ) INSTALL(FILES "OFL.txt" DESTINATION "${LICDIR}" ) INSTALL(FILES "extern/shiny/License.txt" DESTINATION "${LICDIR}" RENAME "Shiny License.txt" ) ENDIF (DPKG_PROGRAM) @@ -458,7 +457,6 @@ if(WIN32) "${OpenMW_SOURCE_DIR}/GPL3.txt" "${OpenMW_SOURCE_DIR}/OFL.txt" "${OpenMW_SOURCE_DIR}/DejaVu Font License.txt" - "${OpenMW_SOURCE_DIR}/Daedric Font License.txt" "${OpenMW_BINARY_DIR}/settings-default.cfg" "${OpenMW_BINARY_DIR}/transparency-overrides.cfg" "${OpenMW_BINARY_DIR}/Release/openmw.exe" From 5a071b0f81515b43fdc26ccb230168bfca2554ed Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Thu, 7 Nov 2013 23:05:45 +0100 Subject: [PATCH 188/434] necessary dpkg rules to get opencs building and packaged on dpkg systems --- CMakeLists.txt | 2 +- apps/opencs/CMakeLists.txt | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7f31a5e533..b90d64666d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -431,7 +431,7 @@ IF(NOT WIN32 AND NOT APPLE) Data files from the original game is required to run it.") SET(CPACK_DEBIAN_PACKAGE_NAME "openmw") SET(CPACK_DEBIAN_PACKAGE_VERSION "${VERSION_STRING}") - SET(CPACK_PACKAGE_EXECUTABLES "openmw;OpenMW bsatool;Bsatool esmtool;Esmtool omwlauncher;OMWLauncher mwiniimporter;MWiniImporter") + SET(CPACK_PACKAGE_EXECUTABLES "openmw;OpenMW opencs;OpenCS bsatool;Bsatool esmtool;Esmtool omwlauncher;OMWLauncher mwiniimporter;MWiniImporter") SET(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.11.2), libfreetype6 (>= 2.2.1), libgcc1 (>= 1:4.1.1), libmpg123-0 (>= 1.12.1), libopenal1 (>= 1:1.12.854), libsndfile1 (>= 1.0.23), libstdc++6 (>= 4.4.5), libuuid1 (>= 2.17.2), libqtgui4 (>= 4.7.0)") SET(CPACK_DEBIAN_PACKAGE_SECTION "Games") diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index d317331e51..7498044ab5 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -160,3 +160,8 @@ target_link_libraries(opencs ${QT_LIBRARIES} components ) + +if(DPKG_PROGRAM) + INSTALL(TARGETS opencs RUNTIME DESTINATION games COMPONENT opencs) +endif() + From 0745a86039596ce29289c6fe88236df50c370ea2 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 8 Nov 2013 11:51:59 +0100 Subject: [PATCH 189/434] added InfoCreator --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/view/world/infocreator.cpp | 79 ++++++++++++++++++++++++++ apps/opencs/view/world/infocreator.hpp | 42 ++++++++++++++ apps/opencs/view/world/subviews.cpp | 5 +- 4 files changed, 125 insertions(+), 3 deletions(-) create mode 100644 apps/opencs/view/world/infocreator.cpp create mode 100644 apps/opencs/view/world/infocreator.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 5e6b853e86..f6f1be02bd 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -60,7 +60,7 @@ opencs_hdrs_noqt (view/doc opencs_units (view/world table tablesubview scriptsubview util regionmapsubview tablebottombox creator genericcreator cellcreator referenceablecreator referencecreator scenesubview scenetoolbar scenetool - scenetoolmode + scenetoolmode infocreator ) opencs_units_noqt (view/world diff --git a/apps/opencs/view/world/infocreator.cpp b/apps/opencs/view/world/infocreator.cpp new file mode 100644 index 0000000000..dcc943d547 --- /dev/null +++ b/apps/opencs/view/world/infocreator.cpp @@ -0,0 +1,79 @@ + +#include "infocreator.hpp" + +#include + +#include +#include +#include + +#include "../../model/world/data.hpp" +#include "../../model/world/commands.hpp" +#include "../../model/world/columns.hpp" +#include "../../model/world/idtable.hpp" + +std::string CSVWorld::InfoCreator::getId() const +{ + std::string id = mTopic->text().toUtf8().constData(); + + std::string unique = QUuid::createUuid().toByteArray().data(); + + unique.erase (std::remove (unique.begin(), unique.end(), '-'), unique.end()); + + unique = unique.substr (1, unique.size()-2); + + return id + '#' + unique; +} + +void CSVWorld::InfoCreator::configureCreateCommand (CSMWorld::CreateCommand& command) const +{ + int index = + dynamic_cast (*getData().getTableModel (getCollectionId())). + findColumnIndex ( + getCollectionId().getType()==CSMWorld::UniversalId::Type_TopicInfos ? + CSMWorld::Columns::ColumnId_Topic : CSMWorld::Columns::ColumnId_Journal); + + command.addValue (index, mTopic->text()); +} + +CSVWorld::InfoCreator::InfoCreator (CSMWorld::Data& data, QUndoStack& undoStack, + const CSMWorld::UniversalId& id) +: GenericCreator (data, undoStack, id) +{ + QLabel *label = new QLabel ("Topic", this); + insertBeforeButtons (label, false); + + mTopic = new QLineEdit (this); + insertBeforeButtons (mTopic, true); + + setManualEditing (false); + + connect (mTopic, SIGNAL (textChanged (const QString&)), this, SLOT (topicChanged())); +} + +void CSVWorld::InfoCreator::reset() +{ + mTopic->setText (""); + GenericCreator::reset(); +} + +std::string CSVWorld::InfoCreator::getErrors() const +{ + // We ignore errors from GenericCreator here, because they can never happen in an InfoCreator. + std::string errors; + + std::string topic = mTopic->text().toUtf8().constData(); + + if ((getCollectionId().getType()==CSMWorld::UniversalId::Type_TopicInfos ? + getData().getTopics() : getData().getJournals()).searchId (topic)==-1) + { + errors += "Invalid Topic ID"; + } + + return errors; +} + +void CSVWorld::InfoCreator::topicChanged() +{ + update(); +} \ No newline at end of file diff --git a/apps/opencs/view/world/infocreator.hpp b/apps/opencs/view/world/infocreator.hpp new file mode 100644 index 0000000000..e9cb7e5960 --- /dev/null +++ b/apps/opencs/view/world/infocreator.hpp @@ -0,0 +1,42 @@ +#ifndef CSV_WORLD_INFOCREATOR_H +#define CSV_WORLD_INFOCREATOR_H + +#include "genericcreator.hpp" + +class QLineEdit; + +namespace CSMWorld +{ + class InfoCollection; +} + +namespace CSVWorld +{ + class InfoCreator : public GenericCreator + { + Q_OBJECT + + QLineEdit *mTopic; + + virtual std::string getId() const; + + virtual void configureCreateCommand (CSMWorld::CreateCommand& command) const; + + public: + + InfoCreator (CSMWorld::Data& data, QUndoStack& undoStack, + const CSMWorld::UniversalId& id); + + virtual void reset(); + + virtual std::string getErrors() const; + ///< Return formatted error descriptions for the current state of the creator. if an empty + /// string is returned, there is no error. + + private slots: + + void topicChanged(); + }; +} + +#endif diff --git a/apps/opencs/view/world/subviews.cpp b/apps/opencs/view/world/subviews.cpp index 85972e7f00..48c32e171d 100644 --- a/apps/opencs/view/world/subviews.cpp +++ b/apps/opencs/view/world/subviews.cpp @@ -15,6 +15,7 @@ #include "referencecreator.hpp" #include "scenesubview.hpp" #include "dialoguecreator.hpp" +#include "infocreator.hpp" void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) { @@ -61,10 +62,10 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) new CSVDoc::SubViewFactoryWithCreator); manager.add (CSMWorld::UniversalId::Type_TopicInfos, - new CSVDoc::SubViewFactoryWithCreator > (false)); + new CSVDoc::SubViewFactoryWithCreator > (false)); manager.add (CSMWorld::UniversalId::Type_JournalInfos, - new CSVDoc::SubViewFactoryWithCreator > (false)); + new CSVDoc::SubViewFactoryWithCreator > (false)); // Subviews for editing/viewing individual records manager.add (CSMWorld::UniversalId::Type_Script, new CSVDoc::SubViewFactory); From 982024a328183c96a985d9eb445c118dafc415b1 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 8 Nov 2013 11:52:30 +0100 Subject: [PATCH 190/434] Topic range access in InfoCollection --- apps/opencs/model/world/collection.hpp | 10 ++++++++ apps/opencs/model/world/infocollection.cpp | 28 +++++++++++++++++++++- apps/opencs/model/world/infocollection.hpp | 11 +++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/apps/opencs/model/world/collection.hpp b/apps/opencs/model/world/collection.hpp index a4cdec4eac..1eee36ae97 100644 --- a/apps/opencs/model/world/collection.hpp +++ b/apps/opencs/model/world/collection.hpp @@ -51,6 +51,10 @@ namespace CSMWorld Collection (const Collection&); Collection& operator= (const Collection&); + protected: + + const std::map& getIdMap() const; + public: Collection(); @@ -128,6 +132,12 @@ namespace CSMWorld ///< \attention This function must not change the ID. }; + template + const std::map& Collection::getIdMap() const + { + return mIndex; + } + template Collection::Collection() {} diff --git a/apps/opencs/model/world/infocollection.cpp b/apps/opencs/model/world/infocollection.cpp index 1be186f760..215354f593 100644 --- a/apps/opencs/model/world/infocollection.cpp +++ b/apps/opencs/model/world/infocollection.cpp @@ -4,6 +4,8 @@ #include #include +#include + void CSMWorld::InfoCollection::load (const Info& record, bool base) { int index = searchId (record.mId); @@ -34,7 +36,8 @@ void CSMWorld::InfoCollection::load (const Info& record, bool base) void CSMWorld::InfoCollection::load (ESM::ESMReader& reader, bool base, const ESM::Dialogue& dialogue) { /// \todo put records into proper order - std::string id = dialogue.mId + "#" + reader.getHNOString ("INAM"); + std::string id = Misc::StringUtils::lowerCase (dialogue.mId) + "#" + + reader.getHNOString ("INAM"); if (reader.isNextSub ("DELE")) { @@ -71,3 +74,26 @@ void CSMWorld::InfoCollection::load (ESM::ESMReader& reader, bool base, const ES load (record, base); } } + +std::pair + CSMWorld::InfoCollection::getTopicRange (const std::string& topic) const +{ + std::string topic2 = Misc::StringUtils::lowerCase (topic); + + MapConstIterator begin = getIdMap().lower_bound (topic2); + + // Skip invalid records: The beginning of a topic string could be identical to another topic + // string. + for (; begin!=getIdMap().end(); ++begin) + if (getRecord (begin->second).get().mTopicId==topic) + break; + + // Find end + MapConstIterator end = begin; + + for (; end!=getIdMap().end(); ++end) + if (getRecord (end->second).get().mTopicId!=topic) + break; + + return std::make_pair (begin, end); +} \ No newline at end of file diff --git a/apps/opencs/model/world/infocollection.hpp b/apps/opencs/model/world/infocollection.hpp index 5faf61a8c7..f92e63e81d 100644 --- a/apps/opencs/model/world/infocollection.hpp +++ b/apps/opencs/model/world/infocollection.hpp @@ -13,11 +13,22 @@ namespace CSMWorld { class InfoCollection : public Collection > { + public: + + typedef std::map::const_iterator MapConstIterator; + + private: + void load (const Info& record, bool base); public: void load (ESM::ESMReader& reader, bool base, const ESM::Dialogue& dialogue); + + std::pair getTopicRange (const std::string& topic) + const; + ///< Return iterators that point to the beginning and past the end of the range for + /// the given topic. }; } From a06aa881cb9518a9770f9d172aaf495b29f0e5d6 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 8 Nov 2013 12:03:03 +0100 Subject: [PATCH 191/434] make sure case handling in info IDs is consistent --- apps/opencs/view/world/infocreator.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/opencs/view/world/infocreator.cpp b/apps/opencs/view/world/infocreator.cpp index dcc943d547..f09222930e 100644 --- a/apps/opencs/view/world/infocreator.cpp +++ b/apps/opencs/view/world/infocreator.cpp @@ -7,6 +7,8 @@ #include #include +#include + #include "../../model/world/data.hpp" #include "../../model/world/commands.hpp" #include "../../model/world/columns.hpp" @@ -14,7 +16,7 @@ std::string CSVWorld::InfoCreator::getId() const { - std::string id = mTopic->text().toUtf8().constData(); + std::string id = Misc::StringUtils::lowerCase (mTopic->text().toUtf8().constData()); std::string unique = QUuid::createUuid().toByteArray().data(); From 3d8da2b9e0dee4d18c3eaf3534cdaf584ceef1a6 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Fri, 8 Nov 2013 12:16:41 +0100 Subject: [PATCH 192/434] proper sorting for newly created records and some case smashing fixes --- apps/opencs/model/world/infocollection.cpp | 29 +++++++++++++++++++--- apps/opencs/model/world/infocollection.hpp | 4 +++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/apps/opencs/model/world/infocollection.cpp b/apps/opencs/model/world/infocollection.cpp index 215354f593..a63d3bce41 100644 --- a/apps/opencs/model/world/infocollection.cpp +++ b/apps/opencs/model/world/infocollection.cpp @@ -1,6 +1,8 @@ #include "infocollection.hpp" +#include + #include #include @@ -17,7 +19,7 @@ void CSMWorld::InfoCollection::load (const Info& record, bool base) record2.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; (base ? record2.mBase : record2.mModified) = record; - appendRecord (record2); + insertRecord (record2, getIdMap().size()); } else { @@ -33,6 +35,27 @@ void CSMWorld::InfoCollection::load (const Info& record, bool base) } } +int CSMWorld::InfoCollection::getAppendIndex (const std::string& id, UniversalId::Type type) const +{ + std::string::size_type separator = id.find_last_of ('#'); + + if (separator==std::string::npos) + throw std::runtime_error ("invalid info ID: " + id); + + std::pair range = getTopicRange (id.substr (0, separator)); + + if (range.first==range.second) + return Collection >::getAppendIndex (id, type); + + int index = 0; + + for (; range.first!=range.second; ++range.first) + if (range.first->second>index) + index = range.first->second; + + return index+1; +} + void CSMWorld::InfoCollection::load (ESM::ESMReader& reader, bool base, const ESM::Dialogue& dialogue) { /// \todo put records into proper order @@ -85,14 +108,14 @@ std::pairsecond).get().mTopicId==topic) + if (Misc::StringUtils::lowerCase (getRecord (begin->second).get().mTopicId)==topic2) break; // Find end MapConstIterator end = begin; for (; end!=getIdMap().end(); ++end) - if (getRecord (end->second).get().mTopicId!=topic) + if (Misc::StringUtils::lowerCase (getRecord (end->second).get().mTopicId)!=topic2) break; return std::make_pair (begin, end); diff --git a/apps/opencs/model/world/infocollection.hpp b/apps/opencs/model/world/infocollection.hpp index f92e63e81d..8ca5f3b45e 100644 --- a/apps/opencs/model/world/infocollection.hpp +++ b/apps/opencs/model/world/infocollection.hpp @@ -23,6 +23,10 @@ namespace CSMWorld public: + virtual int getAppendIndex (const std::string& id, + UniversalId::Type type = UniversalId::Type_None) const; + ///< \param type Will be ignored, unless the collection supports multiple record types + void load (ESM::ESMReader& reader, bool base, const ESM::Dialogue& dialogue); std::pair getTopicRange (const std::string& topic) From cbe96a217020c0424a76a6256b40bc25f34b9a7e Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 9 Nov 2013 07:51:46 +0100 Subject: [PATCH 193/434] Refactor ActiveSpells to track range type. Added basic self range magic. --- apps/openmw/mwbase/world.hpp | 2 + apps/openmw/mwgui/spellicons.cpp | 27 ++++++--- apps/openmw/mwgui/tooltips.cpp | 2 +- apps/openmw/mwmechanics/activespells.cpp | 74 ++++++++++++++++++------ apps/openmw/mwmechanics/activespells.hpp | 26 ++++++++- apps/openmw/mwmechanics/alchemy.cpp | 2 +- apps/openmw/mwmechanics/character.cpp | 5 ++ apps/openmw/mwrender/animation.cpp | 3 + apps/openmw/mwworld/worldimp.cpp | 73 +++++++++++++++++++++++ apps/openmw/mwworld/worldimp.hpp | 2 + 10 files changed, 187 insertions(+), 29 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index c39e878268..8ae563e124 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -406,6 +406,8 @@ namespace MWBase virtual bool getGodModeState() = 0; virtual bool toggleGodMode() = 0; + + virtual void castSpell (const MWWorld::Ptr& actor) = 0; }; } diff --git a/apps/openmw/mwgui/spellicons.cpp b/apps/openmw/mwgui/spellicons.cpp index 0c303485af..5d002792a2 100644 --- a/apps/openmw/mwgui/spellicons.cpp +++ b/apps/openmw/mwgui/spellicons.cpp @@ -97,6 +97,8 @@ namespace MWGui } // add lasting effect spells/potions etc + + // TODO: Move this to ActiveSpells const MWMechanics::ActiveSpells::TContainer& activeSpells = stats.getActiveSpells().getActiveSpells(); for (MWMechanics::ActiveSpells::TContainer::const_iterator it = activeSpells.begin(); it != activeSpells.end(); ++it) @@ -105,31 +107,36 @@ namespace MWGui float timeScale = MWBase::Environment::get().getWorld()->getTimeScaleFactor(); + int i=0; for (std::vector::const_iterator effectIt = list.mList.begin(); - effectIt != list.mList.end(); ++effectIt) + effectIt != list.mList.end(); ++effectIt, ++i) { + if (effectIt->mRange != it->second.mRange) + continue; + + float randomFactor = it->second.mRandom[i]; + const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld ()->getStore ().get().find(effectIt->mEffectID); MagicEffectInfo effectInfo; - effectInfo.mSource = getSpellDisplayName (it->first); + effectInfo.mSource = it->second.mName; effectInfo.mKey = MWMechanics::EffectKey (effectIt->mEffectID); if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill) effectInfo.mKey.mArg = effectIt->mSkill; else if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute) effectInfo.mKey.mArg = effectIt->mAttribute; - effectInfo.mMagnitude = effectIt->mMagnMin + (effectIt->mMagnMax-effectIt->mMagnMin) * it->second.second; + effectInfo.mMagnitude = effectIt->mMagnMin + (effectIt->mMagnMax-effectIt->mMagnMin) * randomFactor; effectInfo.mRemainingTime = effectIt->mDuration + - (it->second.first - MWBase::Environment::get().getWorld()->getTimeStamp())*3600/timeScale; + (it->second.mTimeStamp - MWBase::Environment::get().getWorld()->getTimeStamp())*3600/timeScale; // ingredients need special casing for their magnitude / duration - /// \todo duplicated from ActiveSpells, helper function? if (MWBase::Environment::get().getWorld()->getStore().get().search (it->first)) { - effectInfo.mRemainingTime = effectIt->mDuration * it->second.second + - (it->second.first - MWBase::Environment::get().getWorld()->getTimeStamp())*3600/timeScale; + effectInfo.mRemainingTime = effectIt->mDuration * randomFactor + + (it->second.mTimeStamp - MWBase::Environment::get().getWorld()->getTimeStamp())*3600/timeScale; - effectInfo.mMagnitude = static_cast (0.05*it->second.second / (0.1 * magicEffect->mData.mBaseCost)); + effectInfo.mMagnitude = static_cast (0.05*randomFactor / (0.1 * magicEffect->mData.mBaseCost)); } effects[effectIt->mEffectID].push_back (effectInfo); @@ -288,6 +295,10 @@ namespace MWGui ESM::EffectList SpellIcons::getSpellEffectList (const std::string& id) { + if (const ESM::Enchantment* enchantment = + MWBase::Environment::get().getWorld()->getStore().get().search (id)) + return enchantment->mEffects; + if (const ESM::Spell *spell = MWBase::Environment::get().getWorld()->getStore().get().search (id)) return spell->mEffects; diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 3a609aa91f..85c71575b6 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -467,7 +467,7 @@ namespace MWGui } Widgets::MWDynamicStatPtr chargeWidget = enchantArea->createWidget ("MW_ChargeBar", chargeCoord, MyGUI::Align::Default, "ToolTipEnchantCharge"); - chargeWidget->setValue(charge, charge); + chargeWidget->setValue(charge, maxCharge); totalSize.height += 24; } } diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index 9aca6b7b79..c433ac5e78 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -15,6 +15,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "../mwbase/soundmanager.hpp" #include "../mwworld/class.hpp" @@ -64,15 +65,19 @@ namespace MWMechanics { std::pair > effects = getEffectList (iter->first); - const MWWorld::TimeStamp& start = iter->second.first; - float magnitude = iter->second.second; + const MWWorld::TimeStamp& start = iter->second.mTimeStamp; - for (std::vector::const_iterator iter (effects.first.mList.begin()); - iter!=effects.first.mList.end(); ++iter) + int i = 0; + for (std::vector::const_iterator effectIter (effects.first.mList.begin()); + effectIter!=effects.first.mList.end(); ++effectIter, ++i) { - if (iter->mDuration) + float magnitude = iter->second.mRandom[i]; + if (effectIter->mRange != iter->second.mRange) + continue; + + if (effectIter->mDuration) { - int duration = iter->mDuration; + int duration = effectIter->mDuration; if (effects.second.first) duration *= magnitude; @@ -89,9 +94,9 @@ namespace MWMechanics { const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find ( - iter->mEffectID); + effectIter->mEffectID); - if (iter->mDuration==0) + if (effectIter->mDuration==0) { param.mMagnitude = static_cast (magnitude / (0.1 * magicEffect->mData.mBaseCost)); @@ -104,9 +109,9 @@ namespace MWMechanics } else param.mMagnitude = static_cast ( - (iter->mMagnMax-iter->mMagnMin)*magnitude + iter->mMagnMin); + (effectIter->mMagnMax-effectIter->mMagnMin)*magnitude + effectIter->mMagnMin); - mEffects.add (*iter, param); + mEffects.add (*effectIter, param); } } } @@ -115,6 +120,10 @@ namespace MWMechanics std::pair > ActiveSpells::getEffectList (const std::string& id) const { + if (const ESM::Enchantment* enchantment = + MWBase::Environment::get().getWorld()->getStore().get().search (id)) + return std::make_pair (enchantment->mEffects, std::make_pair(false, false)); + if (const ESM::Spell *spell = MWBase::Environment::get().getWorld()->getStore().get().search (id)) return std::make_pair (spell->mEffects, std::make_pair(false, false)); @@ -156,7 +165,7 @@ namespace MWMechanics : mSpellsChanged (false), mLastUpdate (MWBase::Environment::get().getWorld()->getTimeStamp()) {} - bool ActiveSpells::addSpell (const std::string& id, const MWWorld::Ptr& actor) + bool ActiveSpells::addSpell (const std::string& id, const MWWorld::Ptr& actor, ESM::RangeType range, const std::string& name) { std::pair > effects = getEffectList (id); bool stacks = effects.second.second; @@ -196,11 +205,41 @@ namespace MWMechanics random *= 0.25 * x; } + ActiveSpellParams params; + for (unsigned int i=0; i (std::rand()) / RAND_MAX); + params.mRange = range; + params.mTimeStamp = MWBase::Environment::get().getWorld()->getTimeStamp(); + params.mName = name; + if (iter==mSpells.end() || stacks) - mSpells.insert (std::make_pair (id, - std::make_pair (MWBase::Environment::get().getWorld()->getTimeStamp(), random))); + mSpells.insert (std::make_pair (id, params)); else - iter->second = std::make_pair (MWBase::Environment::get().getWorld()->getTimeStamp(), random); + iter->second = params; + + // Play sounds + for (std::vector::const_iterator iter (effects.first.mList.begin()); + iter!=effects.first.mList.end(); ++iter) + { + if (iter->mRange != range) + continue; + + const ESM::MagicEffect *magicEffect = + MWBase::Environment::get().getWorld()->getStore().get().find ( + iter->mEffectID); + + static const std::string schools[] = { + "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" + }; + + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + if(!magicEffect->mHitSound.empty()) + sndMgr->playSound3D(actor, magicEffect->mHitSound, 1.0f, 1.0f); + else + sndMgr->playSound3D(actor, schools[magicEffect->mData.mSchool]+" hit", 1.0f, 1.0f); + + break; + } mSpellsChanged = true; @@ -249,13 +288,14 @@ namespace MWMechanics duration = iter->mDuration; } - if (effects.second.first) - duration *= iterator->second.second; + // Scale duration by magnitude if needed + if (effects.second.first && iterator->second.mRandom.size()) + duration *= iterator->second.mRandom.front(); double scaledDuration = duration * MWBase::Environment::get().getWorld()->getTimeScaleFactor()/(60*60); - double usedUp = MWBase::Environment::get().getWorld()->getTimeStamp()-iterator->second.first; + double usedUp = MWBase::Environment::get().getWorld()->getTimeStamp()-iterator->second.mTimeStamp; if (usedUp>=scaledDuration) return 0; diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index 8c859b2cb3..e3f882b9aa 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -9,6 +9,8 @@ #include "magiceffects.hpp" +#include + namespace ESM { struct Spell; @@ -22,6 +24,22 @@ namespace MWWorld namespace MWMechanics { + struct ActiveSpellParams + { + // Only apply effects of this range type + ESM::RangeType mRange; + + // When the spell was added + MWWorld::TimeStamp mTimeStamp; + + // Random factor for each effect + std::vector mRandom; + + // Display name, we need this for enchantments, which don't have a name - so you need to supply the + // name of the item with the enchantment to addSpell + std::string mName; + }; + /// \brief Lasting spell effects /// /// \note The name of this class is slightly misleading, since it also handels lasting potion @@ -30,7 +48,7 @@ namespace MWMechanics { public: - typedef std::multimap > TContainer; + typedef std::multimap TContainer; typedef TContainer::const_iterator TIterator; private: @@ -51,9 +69,13 @@ namespace MWMechanics ActiveSpells(); - bool addSpell (const std::string& id, const MWWorld::Ptr& actor); + bool addSpell (const std::string& id, const MWWorld::Ptr& actor, ESM::RangeType range = ESM::RT_Self, const std::string& name = ""); ///< Overwrites an existing spell with the same ID. If the spell does not have any /// non-instant effects, it is ignored. + /// @param id + /// @param actor + /// @param range Only effects with range type \a range will be applied + /// @param name Display name for enchantments, since they don't have a name in their record /// /// \return Has the spell been added? diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index 1d992be413..adfd6d5fce 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -273,7 +273,7 @@ void MWMechanics::Alchemy::addPotion (const std::string& name) newRecord.mName = name; - int index = static_cast (std::rand()/static_cast (RAND_MAX)*6); + int index = static_cast (std::rand()/static_cast (RAND_MAX+1)*6); assert (index>=0 && index<6); static const char *name[] = { "standard", "bargain", "cheap", "fresh", "exclusive", "quality" }; diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index c4260d907d..8c88b3ad03 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -531,6 +531,11 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun else sndMgr->playSound3D(mPtr, schools[effect->mData.mSchool]+" cast", 1.0f, 1.0f); } + if (inv.getSelectedEnchantItem() != inv.end()) + { + // Enchanted items cast immediately (no animation) + MWBase::Environment::get().getWorld()->castSpell(mPtr); + } } else if(mWeaponType == WeapType_PickProbe) { diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 545060fe3a..53d697fdd9 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -653,6 +653,9 @@ void Animation::handleTextKey(AnimState &state, const std::string &groupname, co MWWorld::Class::get(mPtr).hit(mPtr, MWMechanics::CreatureStats::AT_Thrust); else if(evt.compare(off, len, "hit") == 0) MWWorld::Class::get(mPtr).hit(mPtr); + + else if (groupname == "spellcast" && evt.substr(evt.size()-7, 7) == "release") + MWBase::Environment::get().getWorld()->castSpell(mPtr); } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 9635feaf3b..2c8236f3bc 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -19,6 +19,8 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/movement.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/spellsuccess.hpp" + #include "../mwrender/sky.hpp" #include "../mwrender/animation.hpp" @@ -1983,4 +1985,75 @@ namespace MWWorld return mGodMode; } + void World::castSpell(const Ptr &actor) + { + MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); + stats.setAttackingOrSpell(false); + + std::string selectedSpell = stats.getSpells().getSelectedSpell(); + if (!selectedSpell.empty()) + { + const ESM::Spell* spell = getStore().get().search(selectedSpell); + + // Check mana + MWMechanics::DynamicStat magicka = stats.getMagicka(); + if (magicka.getCurrent() < spell->mData.mCost) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientSP}"); + return; + } + + // Reduce mana + magicka.setCurrent(magicka.getCurrent() - spell->mData.mCost); + stats.setMagicka(magicka); + + // Check success + int successChance = MWMechanics::getSpellSuccessChance(selectedSpell, actor); + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + if (roll >= successChance) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicSkillFail}"); + // TODO sound: "Spell Failure " + return; + } + + + actor.getClass().getCreatureStats(actor).getActiveSpells().addSpell(selectedSpell, actor, ESM::RT_Self); + // TODO: RT_Range, RT_Touch + return; + } + + InventoryStore& inv = actor.getClass().getInventoryStore(actor); + if (inv.getSelectedEnchantItem() != inv.end()) + { + MWWorld::Ptr item = *inv.getSelectedEnchantItem(); + std::string id = item.getClass().getEnchantment(item); + const ESM::Enchantment* enchantment = getStore().get().search (id); + + // Check if there's enough charge left + const float enchantCost = enchantment->mData.mCost; + MWMechanics::NpcStats &stats = MWWorld::Class::get(actor).getNpcStats(actor); + int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified(); + const float castCost = enchantCost - (enchantCost / 100) * (eSkill - 10); + + if (item.getCellRef().mEnchantmentCharge == -1) + item.getCellRef().mEnchantmentCharge = enchantment->mData.mCharge; + + if (item.getCellRef().mEnchantmentCharge < castCost) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientCharge}"); + return; + } + + // Reduce charge + item.getCellRef().mEnchantmentCharge -= castCost; + + std::string itemName = item.getClass().getName(item); + actor.getClass().getCreatureStats(actor).getActiveSpells().addSpell(id, actor, ESM::RT_Self, itemName); + + + // TODO: RT_Range, RT_Touch + } + } + } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index e275756e85..6d05df6ff4 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -453,6 +453,8 @@ namespace MWWorld virtual bool getGodModeState(); virtual bool toggleGodMode(); + + virtual void castSpell (const MWWorld::Ptr& actor); }; } From 1051611ffaba6fcf7dbb09830ecd2f7786fa50af Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 9 Nov 2013 07:59:17 +0100 Subject: [PATCH 194/434] Added spell failure sound --- apps/openmw/mwworld/worldimp.cpp | 33 +++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 2c8236f3bc..5b7eb7bb1d 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1996,24 +1996,47 @@ namespace MWWorld const ESM::Spell* spell = getStore().get().search(selectedSpell); // Check mana + bool fail = false; MWMechanics::DynamicStat magicka = stats.getMagicka(); if (magicka.getCurrent() < spell->mData.mCost) { MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientSP}"); - return; + fail = true; } // Reduce mana - magicka.setCurrent(magicka.getCurrent() - spell->mData.mCost); - stats.setMagicka(magicka); + if (!fail) + { + magicka.setCurrent(magicka.getCurrent() - spell->mData.mCost); + stats.setMagicka(magicka); + } // Check success int successChance = MWMechanics::getSpellSuccessChance(selectedSpell, actor); int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] - if (roll >= successChance) + if (!fail && roll >= successChance) { MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicSkillFail}"); - // TODO sound: "Spell Failure " + fail = true; + } + + if (fail) + { + // Failure sound + for (std::vector::const_iterator iter (spell->mEffects.mList.begin()); + iter!=spell->mEffects.mList.end(); ++iter) + { + const ESM::MagicEffect *magicEffect = getStore().get().find ( + iter->mEffectID); + + static const std::string schools[] = { + "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" + }; + + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + sndMgr->playSound3D(actor, "Spell Failure " + schools[magicEffect->mData.mSchool], 1.0f, 1.0f); + break; + } return; } From 976344f0a352c6904c04a6f13996aa140035b520 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 9 Nov 2013 08:07:40 +0100 Subject: [PATCH 195/434] Handle CastOnce enchantments --- apps/openmw/mwworld/worldimp.cpp | 45 ++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 5b7eb7bb1d..a91f1e00c3 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2053,27 +2053,44 @@ namespace MWWorld std::string id = item.getClass().getEnchantment(item); const ESM::Enchantment* enchantment = getStore().get().search (id); - // Check if there's enough charge left - const float enchantCost = enchantment->mData.mCost; - MWMechanics::NpcStats &stats = MWWorld::Class::get(actor).getNpcStats(actor); - int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified(); - const float castCost = enchantCost - (enchantCost / 100) * (eSkill - 10); - if (item.getCellRef().mEnchantmentCharge == -1) - item.getCellRef().mEnchantmentCharge = enchantment->mData.mCharge; - - if (item.getCellRef().mEnchantmentCharge < castCost) + if (enchantment->mData.mType == ESM::Enchantment::WhenUsed) { - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientCharge}"); - return; - } + // Check if there's enough charge left + const float enchantCost = enchantment->mData.mCost; + MWMechanics::NpcStats &stats = MWWorld::Class::get(actor).getNpcStats(actor); + int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified(); + const float castCost = enchantCost - (enchantCost / 100) * (eSkill - 10); - // Reduce charge - item.getCellRef().mEnchantmentCharge -= castCost; + if (item.getCellRef().mEnchantmentCharge == -1) + item.getCellRef().mEnchantmentCharge = enchantment->mData.mCharge; + + if (item.getCellRef().mEnchantmentCharge < castCost) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientCharge}"); + return; + } + + // Reduce charge + item.getCellRef().mEnchantmentCharge -= castCost; + } + if (enchantment->mData.mType == ESM::Enchantment::CastOnce) + { + item.getRefData().setCount(item.getRefData().getCount()-1); + } std::string itemName = item.getClass().getName(item); actor.getClass().getCreatureStats(actor).getActiveSpells().addSpell(id, actor, ESM::RT_Self, itemName); + if (!item.getRefData().getCount()) + { + // Item was used up + MWBase::Environment::get().getWindowManager()->unsetSelectedSpell(); + inv.setSelectedEnchantItem(inv.end()); + } + else + MWBase::Environment::get().getWindowManager()->setSelectedEnchantItem(item); // Set again to show the modified charge + // TODO: RT_Range, RT_Touch } From 3ea7d58ca8b48ed252345fbad73dddbeea1815b4 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 9 Nov 2013 10:34:46 +0100 Subject: [PATCH 196/434] Advance skill on successfull spell cast --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwworld/worldimp.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index f07bbab86b..e2dbf40de4 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -68,7 +68,7 @@ add_openmw_dir (mwclass add_openmw_dir (mwmechanics mechanicsmanagerimp stat character creaturestats magiceffects movement actors objects drawstate spells activespells npcstats aipackage aisequence alchemy aiwander aitravel aifollow - aiescort aiactivate repair enchanting pathfinding security + aiescort aiactivate repair enchanting pathfinding security spellsuccess ) add_openmw_dir (mwbase diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index a91f1e00c3..6c18ec4fd6 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2040,6 +2040,7 @@ namespace MWWorld return; } + actor.getClass().skillUsageSucceeded(actor, MWMechanics::spellSchoolToSkill(MWMechanics::getSpellSchool(selectedSpell, actor)), 0); actor.getClass().getCreatureStats(actor).getActiveSpells().addSpell(selectedSpell, actor, ESM::RT_Self); // TODO: RT_Range, RT_Touch From 3e58655a902ffe96311d8060ca61d3f1377d5b41 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 9 Nov 2013 10:49:00 +0100 Subject: [PATCH 197/434] Implemented Fortify/Drain skill magic effects. Scroll of icarian flight works! --- apps/openmw/mwmechanics/actors.cpp | 15 +++++++++++++++ apps/openmw/mwmechanics/actors.hpp | 3 +++ 2 files changed, 18 insertions(+) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 42851dea39..39276e9645 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -47,6 +47,7 @@ namespace MWMechanics if(!paused) { updateDrowning(ptr, duration); + calculateNpcStatModifiers(ptr); updateEquippedLight(ptr, duration); } } @@ -170,6 +171,20 @@ namespace MWMechanics } } + void Actors::calculateNpcStatModifiers (const MWWorld::Ptr& ptr) + { + NpcStats &npcStats = MWWorld::Class::get(ptr).getNpcStats(ptr); + const MagicEffects &effects = npcStats.getMagicEffects(); + + // skills + for(int i = 0;i < ESM::Skill::Length;++i) + { + Stat& skill = npcStats.getSkill(i); + skill.setModifier(effects.get(EffectKey(ESM::MagicEffect::FortifySkill, i)).mMagnitude - + effects.get(EffectKey(ESM::MagicEffect::DrainSkill, i)).mMagnitude); + } + } + void Actors::updateDrowning(const MWWorld::Ptr& ptr, float duration) { MWBase::World *world = MWBase::Environment::get().getWorld(); diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index a77e52ba30..eeef22635c 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -34,11 +34,14 @@ namespace MWMechanics void updateNpc(const MWWorld::Ptr &ptr, float duration, bool paused); + + void adjustMagicEffects (const MWWorld::Ptr& creature); void calculateDynamicStats (const MWWorld::Ptr& ptr); void calculateCreatureStatModifiers (const MWWorld::Ptr& ptr); + void calculateNpcStatModifiers (const MWWorld::Ptr& ptr); void calculateRestoration (const MWWorld::Ptr& ptr, float duration); From 5c148a3d416c32a582699ea9c5dfd7503e2a727f Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 9 Nov 2013 11:09:44 +0100 Subject: [PATCH 198/434] Fix effect source display --- apps/openmw/mwgui/spellicons.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/spellicons.cpp b/apps/openmw/mwgui/spellicons.cpp index 5d002792a2..dfbcfc2fff 100644 --- a/apps/openmw/mwgui/spellicons.cpp +++ b/apps/openmw/mwgui/spellicons.cpp @@ -120,7 +120,10 @@ namespace MWGui MWBase::Environment::get().getWorld ()->getStore ().get().find(effectIt->mEffectID); MagicEffectInfo effectInfo; - effectInfo.mSource = it->second.mName; + if (it->second.mName != "") + effectInfo.mSource = it->second.mName; + else + effectInfo.mSource = getSpellDisplayName(it->first); effectInfo.mKey = MWMechanics::EffectKey (effectIt->mEffectID); if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill) effectInfo.mKey.mArg = effectIt->mSkill; From 935d9241d8ee0b74f9e414cbfde93b13369148b1 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 9 Nov 2013 11:42:19 +0100 Subject: [PATCH 199/434] first attempt at proper info record loading: partially incorrect and way too slow --- apps/opencs/model/world/infocollection.cpp | 37 ++++++++++++++++++++-- apps/opencs/model/world/infocollection.hpp | 5 +++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/apps/opencs/model/world/infocollection.cpp b/apps/opencs/model/world/infocollection.cpp index a63d3bce41..d326543fbf 100644 --- a/apps/opencs/model/world/infocollection.cpp +++ b/apps/opencs/model/world/infocollection.cpp @@ -2,6 +2,7 @@ #include "infocollection.hpp" #include +#include #include #include @@ -19,7 +20,27 @@ void CSMWorld::InfoCollection::load (const Info& record, bool base) record2.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; (base ? record2.mBase : record2.mModified) = record; - insertRecord (record2, getIdMap().size()); + int index = -1; + + std::string topic = Misc::StringUtils::lowerCase (record2.get().mTopicId); + + if (!record2.get().mPrev.empty()) + { + index = getIndex (record2.get().mPrev, topic); + + if (index!=-1) + ++index; + } + + if (index==-1 && !record2.get().mNext.empty()) + { + index = getIndex (record2.get().mNext, topic); + } + + if (index==-1) + index = getIdMap().size(); + + insertRecord (record2, index); } else { @@ -35,6 +56,19 @@ void CSMWorld::InfoCollection::load (const Info& record, bool base) } } +int CSMWorld::InfoCollection::getIndex (const std::string& id, const std::string& topic) const +{ + std::string fullId = Misc::StringUtils::lowerCase (topic) + "#" + id; + + std::pair range = getTopicRange (topic); + + for (; range.first!=range.second; ++range.first) + if (range.first->first==fullId) + return std::distance (getIdMap().begin(), range.first); + + return -1; +} + int CSMWorld::InfoCollection::getAppendIndex (const std::string& id, UniversalId::Type type) const { std::string::size_type separator = id.find_last_of ('#'); @@ -58,7 +92,6 @@ int CSMWorld::InfoCollection::getAppendIndex (const std::string& id, UniversalId void CSMWorld::InfoCollection::load (ESM::ESMReader& reader, bool base, const ESM::Dialogue& dialogue) { - /// \todo put records into proper order std::string id = Misc::StringUtils::lowerCase (dialogue.mId) + "#" + reader.getHNOString ("INAM"); diff --git a/apps/opencs/model/world/infocollection.hpp b/apps/opencs/model/world/infocollection.hpp index 8ca5f3b45e..398f9becca 100644 --- a/apps/opencs/model/world/infocollection.hpp +++ b/apps/opencs/model/world/infocollection.hpp @@ -21,6 +21,11 @@ namespace CSMWorld void load (const Info& record, bool base); + int getIndex (const std::string& id, const std::string& topic) const; + ///< Return index for record \a id or -1 (if not present; deleted records are considered) + /// + /// \param id info ID without topic prefix + public: virtual int getAppendIndex (const std::string& id, From 23b8206bdc86770e5f4dd68908936ec541ee4563 Mon Sep 17 00:00:00 2001 From: Emanuel Guevel Date: Tue, 13 Aug 2013 01:19:33 +0200 Subject: [PATCH 200/434] Add remove methods to MWWorld::ContainerStore --- apps/openmw/mwworld/containerstore.cpp | 34 ++++++++++++++++++++++++++ apps/openmw/mwworld/containerstore.hpp | 10 ++++++++ apps/openmw/mwworld/inventorystore.cpp | 5 ++++ apps/openmw/mwworld/inventorystore.hpp | 6 +++++ 4 files changed, 55 insertions(+) diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index c6768f5fdd..acc4d4c308 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -198,6 +198,40 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImpl (const Ptr& ptr return it; } +int MWWorld::ContainerStore::remove(const std::string& itemId, int count, const Ptr& actor) +{ + int toRemove = count; + + for (ContainerStoreIterator iter(begin()); iter != end() && toRemove > 0; ++iter) + if (Misc::StringUtils::ciEqual(iter->getCellRef().mRefID, itemId)) + toRemove -= remove(*iter, toRemove, actor); + + // number of removed items + return count - toRemove; +} + +int MWWorld::ContainerStore::remove(const Ptr& item, int count, const Ptr& actor) +{ + assert(this == item.getContainerStore()); + + int toRemove = count; + RefData& itemRef = item.getRefData(); + + if (itemRef.getCount() <= toRemove) + { + toRemove -= itemRef.getCount(); + itemRef.setCount(0); + } + else + { + itemRef.setCount(itemRef.getCount() - toRemove); + toRemove = 0; + } + + // number of removed items + return count - toRemove; +} + void MWWorld::ContainerStore::fill (const ESM::InventoryList& items, const std::string& owner, const MWWorld::ESMStore& store) { for (std::vector::const_iterator iter (items.mList.begin()); iter!=items.mList.end(); diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index 9a11f1603b..d19f1ad50f 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -75,6 +75,16 @@ namespace MWWorld /// /// @return if stacking happened, return iterator to the item that was stacked against, otherwise iterator to the newly inserted item. + int remove(const std::string& itemId, int count, const Ptr& actor); + ///< Remove \a count item(s) designated by \a itemId from this container. + /// + /// @return the number of items actually removed + + virtual int remove(const Ptr& item, int count, const Ptr& actor); + ///< Remove \a count item(s) designated by \a item from this inventory. + /// + /// @return the number of items actually removed + protected: ContainerStoreIterator addImpl (const Ptr& ptr); ///< Add the item to this container (no stacking) diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 98cb6d347b..2aba0b5489 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -325,3 +325,8 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getSelectedEnchantItem( { return mSelectedEnchantItem; } + +int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor) +{ + return ContainerStore::remove(item, count, actor); +} diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index f0cba0f9fe..f1168dda1a 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -108,6 +108,12 @@ namespace MWWorld ///< @return true if the two specified objects can stack with each other /// @note ptr1 is the item that is already in this container + virtual int remove(const Ptr& item, int count, const Ptr& actor); + ///< Remove \a count item(s) designated by \a item from this inventory. + /// + /// \todo check if the item is equipped and do stuff + /// + /// @return the number of items actually removed }; } From 10abb9d297fbaab2ea9021f256ce15e15d19343f Mon Sep 17 00:00:00 2001 From: Emanuel Guevel Date: Tue, 13 Aug 2013 01:19:33 +0200 Subject: [PATCH 201/434] Call ContainerStore::remove() to remove items from inventory Make placeObject() and dropObjectOnGround() in MWWorld to copy objects (and indicate it clearly). Enchanting an item now unequips it. --- apps/openmw/mwbase/world.hpp | 11 ++-- apps/openmw/mwclass/potion.cpp | 6 ++- apps/openmw/mwgui/containeritemmodel.cpp | 4 +- apps/openmw/mwgui/hud.cpp | 9 +--- apps/openmw/mwgui/inventoryitemmodel.cpp | 16 ++---- apps/openmw/mwgui/tradewindow.cpp | 22 ++------ apps/openmw/mwmechanics/actors.cpp | 4 +- apps/openmw/mwmechanics/alchemy.cpp | 3 +- apps/openmw/mwmechanics/enchanting.cpp | 43 ++++++--------- apps/openmw/mwmechanics/enchanting.hpp | 1 - apps/openmw/mwmechanics/repair.cpp | 7 +-- apps/openmw/mwmechanics/security.cpp | 5 +- apps/openmw/mwscript/containerextensions.cpp | 33 ++---------- apps/openmw/mwscript/miscextensions.cpp | 57 ++++++-------------- apps/openmw/mwworld/actioneat.cpp | 6 ++- apps/openmw/mwworld/worldimp.cpp | 26 ++++----- apps/openmw/mwworld/worldimp.hpp | 11 ++-- 17 files changed, 101 insertions(+), 163 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 8ae563e124..4eb9d30880 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -309,14 +309,19 @@ namespace MWBase virtual void update (float duration, bool paused) = 0; - virtual bool placeObject(const MWWorld::Ptr& object, float cursorX, float cursorY) = 0; - ///< place an object into the gameworld at the specified cursor position + virtual bool placeObject (const MWWorld::Ptr& object, float cursorX, float cursorY, int amount) = 0; + ///< copy and place an object into the gameworld at the specified cursor position /// @param object /// @param cursor X (relative 0-1) /// @param cursor Y (relative 0-1) + /// @param number of objects to place /// @return true if the object was placed, or false if it was rejected because the position is too far away - virtual void dropObjectOnGround (const MWWorld::Ptr& actor, const MWWorld::Ptr& object) = 0; + virtual void dropObjectOnGround (const MWWorld::Ptr& actor, const MWWorld::Ptr& object, int amount) = 0; + ///< copy and place an object into the gameworld at the given actor's position + /// @param actor giving the dropped object position + /// @param object + /// @param number of objects to place virtual bool canPlaceObject (float cursorX, float cursorY) = 0; ///< @return true if it is possible to place on object at specified cursor location diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 08683a6684..2f9e63d138 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -11,6 +11,7 @@ #include "../mwworld/actiontake.hpp" #include "../mwworld/actionapply.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwworld/containerstore.hpp" #include "../mwworld/physicssystem.hpp" #include "../mwworld/player.hpp" #include "../mwworld/nullaction.hpp" @@ -164,10 +165,11 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - ptr.getRefData().setCount (ptr.getRefData().getCount()-1); - MWWorld::Ptr actor = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + // remove used potion (assume it is present in inventory) + ptr.getContainerStore()->remove(ptr, 1, actor); + boost::shared_ptr action ( new MWWorld::ActionApply (actor, ref->mBase->mId)); diff --git a/apps/openmw/mwgui/containeritemmodel.cpp b/apps/openmw/mwgui/containeritemmodel.cpp index eff8fbcc1b..6b0fbd8903 100644 --- a/apps/openmw/mwgui/containeritemmodel.cpp +++ b/apps/openmw/mwgui/containeritemmodel.cpp @@ -94,9 +94,7 @@ void ContainerItemModel::removeItem (const ItemStack& item, size_t count) { if (stacks(*it, item.mBase)) { - int refCount = it->getRefData().getCount(); - it->getRefData().setCount(std::max(0, refCount - toRemove)); - toRemove -= refCount; + toRemove -= store.remove(*it, toRemove, *source); if (toRemove <= 0) return; } diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index e7b9f9c015..f0843834d5 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -243,21 +243,16 @@ namespace MWGui float mouseX = cursorPosition.left / float(viewSize.width); float mouseY = cursorPosition.top / float(viewSize.height); - int origCount = object.getRefData().getCount(); - object.getRefData().setCount(mDragAndDrop->mDraggedCount); - if (world->canPlaceObject(mouseX, mouseY)) - world->placeObject(object, mouseX, mouseY); + world->placeObject(object, mouseX, mouseY, mDragAndDrop->mDraggedCount); else - world->dropObjectOnGround(world->getPlayer().getPlayer(), object); + world->dropObjectOnGround(world->getPlayer().getPlayer(), object, mDragAndDrop->mDraggedCount); MWBase::Environment::get().getWindowManager()->changePointer("arrow"); std::string sound = MWWorld::Class::get(object).getDownSoundId(object); MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); - object.getRefData().setCount(origCount); - // remove object from the container it was coming from mDragAndDrop->mSourceModel->removeItem(mDragAndDrop->mItem, mDragAndDrop->mDraggedCount); mDragAndDrop->finish(); diff --git a/apps/openmw/mwgui/inventoryitemmodel.cpp b/apps/openmw/mwgui/inventoryitemmodel.cpp index 62a5a75f06..712e1b6c64 100644 --- a/apps/openmw/mwgui/inventoryitemmodel.cpp +++ b/apps/openmw/mwgui/inventoryitemmodel.cpp @@ -52,18 +52,12 @@ void InventoryItemModel::copyItem (const ItemStack& item, size_t count) void InventoryItemModel::removeItem (const ItemStack& item, size_t count) { MWWorld::ContainerStore& store = MWWorld::Class::get(mActor).getContainerStore(mActor); + int removed = store.remove(item.mBase, count, mActor); - for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) - { - if (*it == item.mBase) - { - if (it->getRefData().getCount() < static_cast(count)) - throw std::runtime_error("Not enough items in the stack to remove"); - it->getRefData().setCount(it->getRefData().getCount() - count); - return; - } - } - throw std::runtime_error("Item to remove not found in container store"); + if (removed == 0) + throw std::runtime_error("Item to remove not found in container store"); + else if (removed < count) + throw std::runtime_error("Not enough items in the stack to remove"); } void InventoryItemModel::update() diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index 94141b1a0a..b6b49b3551 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -202,31 +202,19 @@ namespace MWGui void TradeWindow::addOrRemoveGold(int amount) { - bool goldFound = false; - MWWorld::Ptr gold; MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); MWWorld::ContainerStore& playerStore = MWWorld::Class::get(player).getContainerStore(player); - for (MWWorld::ContainerStoreIterator it = playerStore.begin(); - it != playerStore.end(); ++it) + if (amount > 0) { - if (Misc::StringUtils::ciEqual(it->getCellRef().mRefID, "gold_001")) - { - goldFound = true; - gold = *it; - } - } - if (goldFound) - { - gold.getRefData().setCount(gold.getRefData().getCount() + amount); - } - else - { - assert(amount > 0); MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), "Gold_001"); ref.getPtr().getRefData().setCount(amount); playerStore.add(ref.getPtr(), player); } + else + { + playerStore.remove("gold_001", - amount, player); + } } void TradeWindow::onFrame(float frameDuration) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 39276e9645..175d334628 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -244,7 +244,7 @@ namespace MWMechanics heldIter->getClass().setRemainingUsageTime(*heldIter, timeRemaining); else { - heldIter->getRefData().setCount(0); // remove it + inventoryStore.remove(*heldIter, 1, ptr); // remove it return; } } @@ -253,7 +253,7 @@ namespace MWMechanics // Both NPC and player lights extinguish in water. if(MWBase::Environment::get().getWorld()->isSwimming(ptr)) { - heldIter->getRefData().setCount(0); // remove it + inventoryStore.remove(*heldIter, 1, ptr); // remove it // ...But, only the player makes a sound. if(isPlayer) diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index adfd6d5fce..ab35dbdb1f 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -240,7 +240,8 @@ void MWMechanics::Alchemy::removeIngredients() for (TIngredientsContainer::iterator iter (mIngredients.begin()); iter!=mIngredients.end(); ++iter) if (!iter->isEmpty()) { - iter->getRefData().setCount (iter->getRefData().getCount()-1); + iter->getContainerStore()->remove(*iter, 1, mAlchemist); + if (iter->getRefData().getCount()<1) { needsUpdate = true; diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index 4e26b5027c..7a8a8f5f0b 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -14,7 +14,6 @@ namespace MWMechanics Enchanting::Enchanting() : mCastStyle(ESM::Enchantment::CastOnce) , mSelfEnchanting(false) - , mOldItemCount(0) {} void Enchanting::setOldItem(MWWorld::Ptr oldItem) @@ -24,7 +23,6 @@ namespace MWMechanics { mObjectType = mOldItemPtr.getTypeName(); mOldItemId = mOldItemPtr.getCellRef().mRefID; - mOldItemCount = mOldItemPtr.getRefData().getCount(); } else { @@ -55,17 +53,18 @@ namespace MWMechanics bool Enchanting::create() { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + const MWWorld::Ptr& player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::ContainerStore& store = MWWorld::Class::get(player).getContainerStore(player); ESM::Enchantment enchantment; enchantment.mData.mCharge = getGemCharge(); - mSoulGemPtr.getRefData().setCount (mSoulGemPtr.getRefData().getCount()-1); + store.remove(mSoulGemPtr, 1, player); //Exception for Azura Star, new one will be added after enchanting if(boost::iequals(mSoulGemPtr.get()->mBase->mId, "Misc_SoulGem_Azura")) { MWWorld::ManualRef azura (MWBase::Environment::get().getWorld()->getStore(), "Misc_SoulGem_Azura"); - MWWorld::Class::get (player).getContainerStore (player).add (azura.getPtr(), player); + store.add(azura.getPtr(), player); } if(mSelfEnchanting) @@ -84,16 +83,19 @@ namespace MWMechanics enchantment.mData.mCost = getEnchantPoints(); enchantment.mEffects = mEffectList; - const ESM::Enchantment *enchantmentPtr = MWBase::Environment::get().getWorld()->createRecord (enchantment); - - MWWorld::Class::get(mOldItemPtr).applyEnchantment(mOldItemPtr, enchantmentPtr->mId, getGemCharge(), mNewItemName); - - mOldItemPtr.getRefData().setCount(1); - + // Create a new item MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), mOldItemId); - ref.getPtr().getRefData().setCount (mOldItemCount-1); + const MWWorld::Ptr& newItemPtr = ref.getPtr(); + newItemPtr.getRefData().setCount(1); + + // Apply the enchantment + const ESM::Enchantment *enchantmentPtr = MWBase::Environment::get().getWorld()->createRecord (enchantment); + MWWorld::Class::get(newItemPtr).applyEnchantment(newItemPtr, enchantmentPtr->mId, getGemCharge(), mNewItemName); + + // Add the new item to player inventory and remove the old one + store.add(newItemPtr, player); + store.remove(mOldItemPtr, 1, player); - MWWorld::Class::get (player).getContainerStore (player).add (ref.getPtr(), player); if(!mSelfEnchanting) payForEnchantment(); @@ -299,20 +301,9 @@ namespace MWMechanics void Enchanting::payForEnchantment() const { - MWWorld::Ptr gold; - - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + const MWWorld::Ptr& player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); MWWorld::ContainerStore& store = MWWorld::Class::get(player).getContainerStore(player); - for (MWWorld::ContainerStoreIterator it = store.begin(); - it != store.end(); ++it) - { - if (Misc::StringUtils::ciEqual(it->getCellRef().mRefID, "gold_001")) - { - gold = *it; - } - } - - gold.getRefData().setCount(gold.getRefData().getCount() - getEnchantPrice()); + store.remove("gold_001", getEnchantPrice(), player); } } diff --git a/apps/openmw/mwmechanics/enchanting.hpp b/apps/openmw/mwmechanics/enchanting.hpp index a25fd43abc..988ce41fc7 100644 --- a/apps/openmw/mwmechanics/enchanting.hpp +++ b/apps/openmw/mwmechanics/enchanting.hpp @@ -22,7 +22,6 @@ namespace MWMechanics std::string mNewItemName; std::string mObjectType; std::string mOldItemId; - int mOldItemCount; public: Enchanting(); diff --git a/apps/openmw/mwmechanics/repair.cpp b/apps/openmw/mwmechanics/repair.cpp index 66c492bf8b..38b2a48d7b 100644 --- a/apps/openmw/mwmechanics/repair.cpp +++ b/apps/openmw/mwmechanics/repair.cpp @@ -85,7 +85,10 @@ void Repair::repair(const MWWorld::Ptr &itemToRepair) // tool used up? if (mTool.getCellRef().mCharge == 0) { - mTool.getRefData().setCount(0); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::ContainerStore& store = MWWorld::Class::get(player).getContainerStore(player); + + store.remove(mTool, 1, player); std::string message = MWBase::Environment::get().getWorld()->getStore().get() .find("sNotifyMessage51")->getString(); @@ -93,8 +96,6 @@ void Repair::repair(const MWWorld::Ptr &itemToRepair) MWBase::Environment::get().getWindowManager()->messageBox((boost::format(message) % MWWorld::Class::get(mTool).getName(mTool)).str()); // try to find a new tool of the same ID - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); - MWWorld::ContainerStore& store = MWWorld::Class::get(player).getContainerStore(player); for (MWWorld::ContainerStoreIterator iter (store.begin()); iter!=store.end(); ++iter) { diff --git a/apps/openmw/mwmechanics/security.cpp b/apps/openmw/mwmechanics/security.cpp index d19da6e2af..c373e83f51 100644 --- a/apps/openmw/mwmechanics/security.cpp +++ b/apps/openmw/mwmechanics/security.cpp @@ -2,6 +2,7 @@ #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" +#include "../mwworld/containerstore.hpp" #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" @@ -61,7 +62,7 @@ namespace MWMechanics lockpick.getCellRef().mCharge = lockpick.get()->mBase->mData.mUses; --lockpick.getCellRef().mCharge; if (!lockpick.getCellRef().mCharge) - lockpick.getRefData().setCount(0); + lockpick.getContainerStore()->remove(lockpick, 1, mActor); } void Security::probeTrap(const MWWorld::Ptr &trap, const MWWorld::Ptr &probe, @@ -103,7 +104,7 @@ namespace MWMechanics probe.getCellRef().mCharge = probe.get()->mBase->mData.mUses; --probe.getCellRef().mCharge; if (!probe.getCellRef().mCharge) - probe.getRefData().setCount(0); + probe.getContainerStore()->remove(probe, 1, mActor); } } diff --git a/apps/openmw/mwscript/containerextensions.cpp b/apps/openmw/mwscript/containerextensions.cpp index 2f3ef2d792..a5cdfc5e29 100644 --- a/apps/openmw/mwscript/containerextensions.cpp +++ b/apps/openmw/mwscript/containerextensions.cpp @@ -136,41 +136,18 @@ namespace MWScript return; MWWorld::ContainerStore& store = MWWorld::Class::get (ptr).getContainerStore (ptr); - + std::string itemName = ""; - // originalCount holds the total number of items to remove, count holds the remaining number of items to remove - Interpreter::Type_Integer originalCount = count; + int numRemoved = store.remove(item, count, ptr); - for (MWWorld::ContainerStoreIterator iter (store.begin()); iter!=store.end() && count; - ++iter) - { - if (Misc::StringUtils::ciEqual(iter->getCellRef().mRefID, item)) - { - itemName = MWWorld::Class::get(*iter).getName(*iter); - - if (iter->getRefData().getCount()<=count) - { - count -= iter->getRefData().getCount(); - iter->getRefData().setCount (0); - } - else - { - iter->getRefData().setCount (iter->getRefData().getCount()-count); - count = 0; - } - } - } - // Spawn a messagebox (only for items removed from player's inventory) - if (ptr == MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer()) + if ((numRemoved > 0) + && (ptr == MWBase::Environment::get().getWorld()->getPlayer().getPlayer())) { // The two GMST entries below expand to strings informing the player of what, and how many of it has been removed from their inventory std::string msgBox; - int numRemoved = (originalCount - count); - if (numRemoved == 0) - return; - + if(numRemoved > 1) { msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage63}"); diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 4ae1136e22..1a2de650fc 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -374,18 +374,7 @@ namespace MWScript MWWorld::ContainerStore& store = MWWorld::Class::get (ptr).getContainerStore (ptr); - - for (MWWorld::ContainerStoreIterator iter (store.begin()); iter!=store.end(); ++iter) - { - if (::Misc::StringUtils::ciEqual(iter->getCellRef().mSoul, soul)) - { - if (iter->getRefData().getCount() <= 1) - iter->getRefData().setCount (0); - else - iter->getRefData().setCount (iter->getRefData().getCount() - 1); - break; - } - } + store.remove(soul, 1, ptr); } }; @@ -415,24 +404,18 @@ namespace MWScript MWWorld::ContainerStore& store = MWWorld::Class::get (ptr).getContainerStore (ptr); + int toRemove = amount; for (MWWorld::ContainerStoreIterator iter (store.begin()); iter!=store.end(); ++iter) { if (::Misc::StringUtils::ciEqual(iter->getCellRef().mRefID, item)) { - if(iter->getRefData().getCount() <= amount) - { - MWBase::Environment::get().getWorld()->dropObjectOnGround(ptr, *iter); - iter->getRefData().setCount(0); - } - else - { - int original = iter->getRefData().getCount(); - iter->getRefData().setCount(amount); - MWBase::Environment::get().getWorld()->dropObjectOnGround(ptr, *iter); - iter->getRefData().setCount(original - amount); - } + int removed = store.remove(*iter, toRemove, ptr); + MWBase::Environment::get().getWorld()->dropObjectOnGround(ptr, *iter, removed); - break; + toRemove -= removed; + + if (toRemove <= 0) + break; } } } @@ -458,20 +441,8 @@ namespace MWScript { if (::Misc::StringUtils::ciEqual(iter->getCellRef().mSoul, soul)) { - - if(iter->getRefData().getCount() <= 1) - { - MWBase::Environment::get().getWorld()->dropObjectOnGround(ptr, *iter); - iter->getRefData().setCount(0); - } - else - { - int original = iter->getRefData().getCount(); - iter->getRefData().setCount(1); - MWBase::Environment::get().getWorld()->dropObjectOnGround(ptr, *iter); - iter->getRefData().setCount(original - 1); - } - + MWBase::Environment::get().getWorld()->dropObjectOnGround(ptr, *iter, 1); + store.remove(*iter, 1, ptr); break; } } @@ -545,7 +516,13 @@ namespace MWScript if (ptr.isInCell()) MWBase::Environment::get().getWorld()->deleteObject (ptr); else - ptr.getRefData().setCount(0); + { + MWWorld::ContainerStore* store = ptr.getContainerStore(); + if (store != NULL) + store->remove(ptr, ptr.getRefData().getCount(), ptr); + else + ptr.getRefData().setCount(0); + } } } }; diff --git a/apps/openmw/mwworld/actioneat.cpp b/apps/openmw/mwworld/actioneat.cpp index 63efff738e..470eeda2b3 100644 --- a/apps/openmw/mwworld/actioneat.cpp +++ b/apps/openmw/mwworld/actioneat.cpp @@ -11,6 +11,8 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwworld/containerstore.hpp" + #include "esmstore.hpp" #include "class.hpp" @@ -18,8 +20,8 @@ namespace MWWorld { void ActionEat::executeImp (const Ptr& actor) { - // remove used item - getTarget().getRefData().setCount (getTarget().getRefData().getCount()-1); + // remove used item (assume the item is present in inventory) + getTarget().getContainerStore()->remove(getTarget(), 1, actor); // check for success const MWMechanics::CreatureStats& creatureStats = MWWorld::Class::get (actor).getCreatureStats (actor); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 3ea9f72825..e24a7ed0ad 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1477,7 +1477,7 @@ namespace MWWorld item.getRefData().getLocals().setVarByInt(script, "onpcdrop", 1); } - bool World::placeObject (const Ptr& object, float cursorX, float cursorY) + bool World::placeObject (const MWWorld::Ptr& object, float cursorX, float cursorY, int amount) { std::pair result = mPhysics->castRay(cursorX, cursorY); @@ -1502,9 +1502,14 @@ namespace MWWorld pos.rot[0] = 0; pos.rot[1] = 0; + // copy the object and set its count + int origCount = object.getRefData().getCount(); + object.getRefData().setCount(amount); Ptr dropped = copyObjectToCell(object, *cell, pos); + object.getRefData().setCount(origCount); + + // only the player place items in the world, so no need to check actor PCDropped(dropped); - object.getRefData().setCount(0); return true; } @@ -1549,7 +1554,7 @@ namespace MWWorld return dropped; } - void World::dropObjectOnGround (const Ptr& actor, const Ptr& object) + void World::dropObjectOnGround (const Ptr& actor, const Ptr& object, int amount) { MWWorld::Ptr::CellStore* cell = actor.getCell(); @@ -1570,10 +1575,14 @@ namespace MWWorld mPhysics->castRay(orig, dir, len); pos.pos[2] = hit.second.z; + // copy the object and set its count + int origCount = object.getRefData().getCount(); + object.getRefData().setCount(amount); Ptr dropped = copyObjectToCell(object, *cell, pos); + object.getRefData().setCount(origCount); + if(actor == mPlayer->getPlayer()) // Only call if dropped by player PCDropped(dropped); - object.getRefData().setCount(0); } void World::processChangedSettings(const Settings::CategorySettingVector& settings) @@ -1955,14 +1964,7 @@ namespace MWWorld } else { - ContainerStore &store = Class::get(actor).getContainerStore(actor); - - const std::string item = "WerewolfRobe"; - for(ContainerStoreIterator iter(store.begin());iter != store.end();++iter) - { - if(Misc::StringUtils::ciEqual(iter->getCellRef().mRefID, item)) - iter->getRefData().setCount(0); - } + Class::get(actor).getContainerStore(actor).remove("WerewolfRobe", 1, actor); } if(actor.getRefData().getHandle() == "player") diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 49f26998de..cd982aceee 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -354,14 +354,19 @@ namespace MWWorld virtual void update (float duration, bool paused); - virtual bool placeObject (const Ptr& object, float cursorX, float cursorY); - ///< place an object into the gameworld at the specified cursor position + virtual bool placeObject (const MWWorld::Ptr& object, float cursorX, float cursorY, int amount); + ///< copy and place an object into the gameworld at the specified cursor position /// @param object /// @param cursor X (relative 0-1) /// @param cursor Y (relative 0-1) + /// @param number of objects to place /// @return true if the object was placed, or false if it was rejected because the position is too far away - virtual void dropObjectOnGround (const Ptr& actor, const Ptr& object); + virtual void dropObjectOnGround (const MWWorld::Ptr& actor, const MWWorld::Ptr& object, int amount); + ///< copy and place an object into the gameworld at the given actor's position + /// @param actor giving the dropped object position + /// @param object + /// @param number of objects to place virtual bool canPlaceObject(float cursorX, float cursorY); ///< @return true if it is possible to place on object at specified cursor location From d05baa8c22e5aa3e06824f6fb80d1bfa9206a9f1 Mon Sep 17 00:00:00 2001 From: Emanuel Guevel Date: Fri, 1 Nov 2013 00:54:54 +0100 Subject: [PATCH 202/434] Add method InventoryStore::unequipSlot() This will permit to do run a treatment when an item is unequipped. --- apps/openmw/mwworld/inventorystore.cpp | 37 +++++++++++++++++--------- apps/openmw/mwworld/inventorystore.hpp | 4 +-- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 2aba0b5489..4bda723183 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -129,18 +129,7 @@ void MWWorld::InventoryStore::equip (int slot, const ContainerStoreIterator& ite void MWWorld::InventoryStore::unequipAll(const MWWorld::Ptr& actor) { for (int slot=0; slot < MWWorld::InventoryStore::Slots; ++slot) - { - MWWorld::ContainerStoreIterator it = getSlot(slot); - if (it != end()) - { - equip(slot, end()); - std::string script = MWWorld::Class::get(*it).getScript(*it); - - // Unset OnPCEquip Variable on item's script, if it has a script with that variable declared - if((actor.getRefData().getHandle() == "player") && (script != "")) - (*it).getRefData().getLocals().setVarByInt(script, "onpcequip", 0); - } - } + unequipSlot(slot, actor); } MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getSlot (int slot) @@ -328,5 +317,29 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getSelectedEnchantItem( int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor) { + for (int slot=0; slot < MWWorld::InventoryStore::Slots; ++slot) + { + if (mSlots[slot] == end()) + continue; + + if (*mSlots[slot] == item) + { + unequipSlot(slot, actorPtr); + break; + } + } + return ContainerStore::remove(item, count, actor); } + +void MWWorld::InventoryStore::unequipSlot(int slot, const MWWorld::Ptr& actor) +{ + ContainerStoreIterator it = getSlot(slot); + if (it != end()) + { + equip(slot, end()); + + + /// \todo update actor model + } +} diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index f1168dda1a..fd9f3fa7a5 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -111,9 +111,9 @@ namespace MWWorld virtual int remove(const Ptr& item, int count, const Ptr& actor); ///< Remove \a count item(s) designated by \a item from this inventory. /// - /// \todo check if the item is equipped and do stuff - /// /// @return the number of items actually removed + + void unequipSlot(int slot, const Ptr& actor); }; } From 52cef199821fdf710ba57217198099b9fe1ea977 Mon Sep 17 00:00:00 2001 From: Emanuel Guevel Date: Fri, 1 Nov 2013 00:55:24 +0100 Subject: [PATCH 203/434] Update weapon/magic icons when items are removed from player inventory --- apps/openmw/mwworld/inventorystore.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 4bda723183..63cd46b628 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -8,6 +8,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "../mwbase/windowmanager.hpp" #include "../mwmechanics/npcstats.hpp" @@ -339,6 +340,26 @@ void MWWorld::InventoryStore::unequipSlot(int slot, const MWWorld::Ptr& actor) { equip(slot, end()); + if (actor.getRefData().getHandle() == "player") + { + // Unset OnPCEquip Variable on item's script, if it has a script with that variable declared + const std::string& script = Class::get(*it).getScript(*it); + if (script != "") + (*it).getRefData().getLocals().setVarByInt(script, "onpcequip", 0); + + // Update HUD icon when removing player weapon or selected enchanted item. + // We have to check for both as the weapon could also be the enchanted item. + if (slot == MWWorld::InventoryStore::Slot_CarriedRight) + { + // weapon + MWBase::Environment::get().getWindowManager()->unsetSelectedWeapon(); + } + if ((mSelectedEnchantItem != end()) && (mSelectedEnchantItem == it)) + { + // enchanted item + MWBase::Environment::get().getWindowManager()->unsetSelectedSpell(); + } + } /// \todo update actor model } From 2786530430086fefd03a68201f25d2334ce58b8c Mon Sep 17 00:00:00 2001 From: Emanuel Guevel Date: Tue, 13 Aug 2013 02:06:46 +0200 Subject: [PATCH 204/434] =?UTF-8?q?Edit=20InventoryStore::equip()=20to=20c?= =?UTF-8?q?all=20the=20new=20unequipSlot=20function=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …to unequip previously equipped item. --- apps/openmw/mwgui/inventorywindow.cpp | 2 +- .../mwmechanics/mechanicsmanagerimp.cpp | 2 +- apps/openmw/mwworld/actionequip.cpp | 8 ++--- apps/openmw/mwworld/inventorystore.cpp | 35 ++++++++++--------- apps/openmw/mwworld/inventorystore.hpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 2 +- 6 files changed, 26 insertions(+), 25 deletions(-) diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 4f616b3126..5c8cd2cdc4 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -384,7 +384,7 @@ namespace MWGui MWWorld::ContainerStoreIterator it = invStore.getSlot(slot); if (it != invStore.end() && *it == item) { - invStore.equip(slot, invStore.end()); + invStore.equip(slot, invStore.end(), mPtr); std::string script = MWWorld::Class::get(*it).getScript(*it); // Unset OnPCEquip Variable on item's script, if it has a script with that variable declared diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 8c13db7900..54b02fb529 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -159,7 +159,7 @@ namespace MWMechanics // auto-equip again. we need this for when the race is changed to a beast race MWWorld::InventoryStore& invStore = MWWorld::Class::get(ptr).getInventoryStore(ptr); for (int i=0; i=static_cast (mSlots.size())) throw std::runtime_error ("slot number out of range"); @@ -98,19 +98,8 @@ void MWWorld::InventoryStore::equip (int slot, const ContainerStoreIterator& ite throw std::runtime_error ("invalid slot"); } - // restack item previously in this slot (if required) if (mSlots[slot] != end()) - { - for (MWWorld::ContainerStoreIterator iter (begin()); iter!=end(); ++iter) - { - if (stacks(*iter, *mSlots[slot])) - { - iter->getRefData().setCount( iter->getRefData().getCount() + mSlots[slot]->getRefData().getCount() ); - mSlots[slot]->getRefData().setCount(0); - break; - } - } - } + unequipSlot(slot, actor); // unstack item pointed to by iterator if required if (iterator!=end() && !slots.second && iterator->getRefData().getCount() > 1) // if slots.second is true, item can stay stacked when equipped @@ -214,10 +203,10 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& npc) case 0: continue; case 2: - invStore.equip(MWWorld::InventoryStore::Slot_CarriedLeft, invStore.end()); + invStore.unequipSlot(MWWorld::InventoryStore::Slot_CarriedLeft, npc); break; case 3: - invStore.equip(MWWorld::InventoryStore::Slot_CarriedRight, invStore.end()); + invStore.unequipSlot(MWWorld::InventoryStore::Slot_CarriedRight, npc); break; } @@ -325,7 +314,7 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor if (*mSlots[slot] == item) { - unequipSlot(slot, actorPtr); + unequipSlot(slot, actor); break; } } @@ -338,7 +327,19 @@ void MWWorld::InventoryStore::unequipSlot(int slot, const MWWorld::Ptr& actor) ContainerStoreIterator it = getSlot(slot); if (it != end()) { - equip(slot, end()); + // restack item previously in this slot + for (MWWorld::ContainerStoreIterator iter (begin()); iter != end(); ++iter) + { + if (stacks(*iter, *mSlots[slot])) + { + iter->getRefData().setCount(iter->getRefData().getCount() + mSlots[slot]->getRefData().getCount()); + mSlots[slot]->getRefData().setCount(0); + break; + } + } + + // empty this slot + mSlots[slot] = end(); if (actor.getRefData().getHandle() == "player") { diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index fd9f3fa7a5..9468f928f1 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -76,7 +76,7 @@ namespace MWWorld /// /// @return if stacking happened, return iterator to the item that was stacked against, otherwise iterator to the newly inserted item. - void equip (int slot, const ContainerStoreIterator& iterator); + void equip (int slot, const ContainerStoreIterator& iterator, const Ptr& actor); ///< \note \a iterator can be an end-iterator void setSelectedEnchantItem(const ContainerStoreIterator& iterator); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index e24a7ed0ad..144901fc57 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1960,7 +1960,7 @@ namespace MWWorld // Not sure this is right InventoryStore &inv = Class::get(actor).getInventoryStore(actor); - inv.equip(InventoryStore::Slot_Robe, inv.add(ref.getPtr(), actor)); + inv.equip(InventoryStore::Slot_Robe, inv.add(ref.getPtr(), actor), actor); } else { From 8ff747fbefe67381d2b9ec77f06e22052ad476a5 Mon Sep 17 00:00:00 2001 From: Emanuel Guevel Date: Wed, 16 Oct 2013 18:39:29 +0200 Subject: [PATCH 205/434] Move some deleteObject logic from OpDelete to MWWorld::deleteObject --- apps/openmw/mwscript/miscextensions.cpp | 13 +------------ apps/openmw/mwworld/worldimp.cpp | 9 +++++---- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 1a2de650fc..66354513ea 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -512,18 +512,7 @@ namespace MWScript runtime.pop(); if (parameter == 1) - { - if (ptr.isInCell()) - MWBase::Environment::get().getWorld()->deleteObject (ptr); - else - { - MWWorld::ContainerStore* store = ptr.getContainerStore(); - if (store != NULL) - store->remove(ptr, ptr.getRefData().getCount(), ptr); - else - ptr.getRefData().setCount(0); - } - } + MWBase::Environment::get().getWorld()->deleteObject(ptr); } }; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 144901fc57..affcf03878 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -837,12 +837,13 @@ namespace MWWorld void World::deleteObject (const Ptr& ptr) { - if (ptr.getRefData().getCount()>0) + if (ptr.getRefData().getCount() > 0) { - ptr.getRefData().setCount (0); + ptr.getRefData().setCount(0); - if (mWorldScene->getActiveCells().find (ptr.getCell())!=mWorldScene->getActiveCells().end() && - ptr.getRefData().isEnabled()) + if (ptr.isInCell() + && mWorldScene->getActiveCells().find(ptr.getCell()) != mWorldScene->getActiveCells().end() + && ptr.getRefData().isEnabled()) { mWorldScene->removeObjectFromScene (ptr); mLocalScripts.remove (ptr); From f4f2586e8ce334f5141348ad120f9959d0caa85e Mon Sep 17 00:00:00 2001 From: Emanuel Guevel Date: Mon, 14 Oct 2013 21:49:21 +0200 Subject: [PATCH 206/434] Remove duplicate code for PlaceAtMe/PlaceAtPC using a template --- .../mwscript/transformationextensions.cpp | 69 ++++--------------- 1 file changed, 12 insertions(+), 57 deletions(-) diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 6246daee22..f2d1af5c26 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -450,62 +450,16 @@ namespace MWScript } }; - template - class OpPlaceAtPc : public Interpreter::Opcode0 + template + class OpPlaceAt : public Interpreter::Opcode0 { public: virtual void execute (Interpreter::Runtime& runtime) { - std::string itemID = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); - - Interpreter::Type_Integer count = runtime[0].mInteger; - runtime.pop(); - Interpreter::Type_Float distance = runtime[0].mFloat; - runtime.pop(); - Interpreter::Type_Integer direction = runtime[0].mInteger; - runtime.pop(); - - if (count<0) - throw std::runtime_error ("count must be non-negative"); - - // no-op - if (count == 0) - return; - - ESM::Position ipos = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getRefData().getPosition(); - Ogre::Vector3 pos(ipos.pos[0],ipos.pos[1],ipos.pos[2]); - Ogre::Quaternion rot(Ogre::Radian(-ipos.rot[2]), Ogre::Vector3::UNIT_Z); - if(direction == 0) pos = pos + distance*rot.yAxis(); - else if(direction == 1) pos = pos - distance*rot.yAxis(); - else if(direction == 2) pos = pos - distance*rot.xAxis(); - else if(direction == 3) pos = pos + distance*rot.xAxis(); - else throw std::runtime_error ("direction must be 0,1,2 or 3"); - - ipos.pos[0] = pos.x; - ipos.pos[1] = pos.y; - ipos.pos[2] = pos.z; - ipos.rot[0] = 0; - ipos.rot[1] = 0; - ipos.rot[2] = 0; - - MWWorld::CellStore* store = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getCell(); - MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(),itemID); - ref.getPtr().getCellRef().mPos = ipos; - ref.getPtr().getRefData().setCount(count); - MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),*store,ipos); - } - }; - - template - class OpPlaceAtMe : public Interpreter::Opcode0 - { - public: - - virtual void execute (Interpreter::Runtime& runtime) - { - MWWorld::Ptr me = R()(runtime); + MWWorld::Ptr actor = pc + ? MWBase::Environment::get().getWorld()->getPlayer().getPlayer() + : R()(runtime); std::string itemID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); @@ -524,7 +478,7 @@ namespace MWScript if (count == 0) return; - ESM::Position ipos = me.getRefData().getPosition(); + ESM::Position ipos = actor.getRefData().getPosition(); Ogre::Vector3 pos(ipos.pos[0],ipos.pos[1],ipos.pos[2]); Ogre::Quaternion rot(Ogre::Radian(-ipos.rot[2]), Ogre::Vector3::UNIT_Z); if(direction == 0) pos = pos + distance*rot.yAxis(); @@ -540,12 +494,13 @@ namespace MWScript ipos.rot[1] = 0; ipos.rot[2] = 0; - MWWorld::CellStore* store = me.getCell(); + // create item + MWWorld::CellStore* store = actor.getCell(); MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(),itemID); ref.getPtr().getCellRef().mPos = ipos; ref.getPtr().getRefData().setCount(count); - MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),*store,ipos); + MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),*store,ipos); } }; @@ -730,9 +685,9 @@ namespace MWScript interpreter.installSegment5(Compiler::Transformation::opcodePositionCellExplicit,new OpPositionCell); interpreter.installSegment5(Compiler::Transformation::opcodePlaceItemCell,new OpPlaceItemCell); interpreter.installSegment5(Compiler::Transformation::opcodePlaceItem,new OpPlaceItem); - interpreter.installSegment5(Compiler::Transformation::opcodePlaceAtPc,new OpPlaceAtPc); - interpreter.installSegment5(Compiler::Transformation::opcodePlaceAtMe,new OpPlaceAtMe); - interpreter.installSegment5(Compiler::Transformation::opcodePlaceAtMeExplicit,new OpPlaceAtMe); + interpreter.installSegment5(Compiler::Transformation::opcodePlaceAtPc,new OpPlaceAt); + interpreter.installSegment5(Compiler::Transformation::opcodePlaceAtMe,new OpPlaceAt); + interpreter.installSegment5(Compiler::Transformation::opcodePlaceAtMeExplicit,new OpPlaceAt); interpreter.installSegment5(Compiler::Transformation::opcodeModScale,new OpModScale); interpreter.installSegment5(Compiler::Transformation::opcodeModScaleExplicit,new OpModScale); interpreter.installSegment5(Compiler::Transformation::opcodeRotate,new OpRotate); From 0691978603e42ab05c42845b41ffc4076784b57f Mon Sep 17 00:00:00 2001 From: Emanuel Guevel Date: Sun, 20 Oct 2013 15:49:33 +0200 Subject: [PATCH 207/434] Add item count to ManualRef constructor as optional argument --- apps/openmw/mwworld/manualref.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/manualref.hpp b/apps/openmw/mwworld/manualref.hpp index 6616165fae..1cdcd8484e 100644 --- a/apps/openmw/mwworld/manualref.hpp +++ b/apps/openmw/mwworld/manualref.hpp @@ -37,7 +37,7 @@ namespace MWWorld public: - ManualRef (const MWWorld::ESMStore& store, const std::string& name) + ManualRef (const MWWorld::ESMStore& store, const std::string& name, const int count=1) { // create if (!create (store.get(), name) && @@ -74,6 +74,7 @@ namespace MWWorld cellRef.mTeleport = false; cellRef.mLockLevel = 0; cellRef.mReferenceBlocked = 0; + mPtr.getRefData().setCount(count); } const Ptr& getPtr() const From aefa54d72daa71c8fea030c7d637f212d868ff89 Mon Sep 17 00:00:00 2001 From: Emanuel Guevel Date: Sun, 20 Oct 2013 15:49:43 +0200 Subject: [PATCH 208/434] Pass item count to ManualRef constructor This remove the need to call setCount in multiple places. --- apps/openmw/mwclass/misc.cpp | 1 - apps/openmw/mwgui/tradewindow.cpp | 3 +-- apps/openmw/mwmechanics/enchanting.cpp | 3 +-- apps/openmw/mwscript/containerextensions.cpp | 4 +--- apps/openmw/mwscript/miscextensions.cpp | 4 +--- apps/openmw/mwscript/transformationextensions.cpp | 3 +-- apps/openmw/mwworld/containerstore.cpp | 7 ++----- apps/openmw/mwworld/worldimp.cpp | 1 - 8 files changed, 7 insertions(+), 19 deletions(-) diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index 6247191a92..67f79c40ba 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -219,7 +219,6 @@ namespace MWClass MWWorld::LiveCellRef *ref = newRef.getPtr().get(); newPtr = MWWorld::Ptr(&cell.mMiscItems.insert(*ref), &cell); - newPtr.getRefData ().setCount(1); newPtr.getCellRef().mGoldValue = goldAmount; } else { MWWorld::LiveCellRef *ref = diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index b6b49b3551..c179236087 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -207,8 +207,7 @@ namespace MWGui if (amount > 0) { - MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), "Gold_001"); - ref.getPtr().getRefData().setCount(amount); + MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), "Gold_001", amount); playerStore.add(ref.getPtr(), player); } else diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index 7a8a8f5f0b..5d148d1106 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -84,9 +84,8 @@ namespace MWMechanics enchantment.mEffects = mEffectList; // Create a new item - MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), mOldItemId); + MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), mOldItemId, 1); const MWWorld::Ptr& newItemPtr = ref.getPtr(); - newItemPtr.getRefData().setCount(1); // Apply the enchantment const ESM::Enchantment *enchantmentPtr = MWBase::Environment::get().getWorld()->createRecord (enchantment); diff --git a/apps/openmw/mwscript/containerextensions.cpp b/apps/openmw/mwscript/containerextensions.cpp index a5cdfc5e29..d3ed50838b 100644 --- a/apps/openmw/mwscript/containerextensions.cpp +++ b/apps/openmw/mwscript/containerextensions.cpp @@ -53,10 +53,8 @@ namespace MWScript if (count == 0) return; - MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), item); + MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), item, count); - ref.getPtr().getRefData().setCount (count); - // Configure item's script variables std::string script = MWWorld::Class::get(ref.getPtr()).getScript(ref.getPtr()); if (script != "") diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 66354513ea..35f7a40447 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -348,9 +348,7 @@ namespace MWScript const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); store.get().find(creature); // This line throws an exception if it can't find the creature - MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), gem); - - ref.getPtr().getRefData().setCount (1); + MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), gem, 1); ref.getPtr().getCellRef().mSoul = creature; diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index f2d1af5c26..4b60f47ce2 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -496,9 +496,8 @@ namespace MWScript // create item MWWorld::CellStore* store = actor.getCell(); - MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(),itemID); + MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), itemID, count); ref.getPtr().getCellRef().mPos = ipos; - ref.getPtr().getRefData().setCount(count); MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),*store,ipos); } diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index acc4d4c308..d25b29397a 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -140,11 +140,9 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp (const Ptr& ptr) || Misc::StringUtils::ciEqual(ptr.getCellRef().mRefID, "gold_025") || Misc::StringUtils::ciEqual(ptr.getCellRef().mRefID, "gold_100")) { - MWWorld::ManualRef ref(esmStore, "Gold_001"); - int count = MWWorld::Class::get(ptr).getValue(ptr) * ptr.getRefData().getCount(); + MWWorld::ManualRef ref(esmStore, "Gold_001", count); - ref.getPtr().getRefData().setCount(count); for (MWWorld::ContainerStoreIterator iter (begin(type)); iter!=end(); ++iter) { if (Misc::StringUtils::ciEqual((*iter).get()->mRef.mRefID, "gold_001")) @@ -250,7 +248,7 @@ void MWWorld::ContainerStore::addInitialItem (const std::string& id, const std:: try { - ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), id); + ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), id, count); if (ref.getPtr().getTypeName()==typeid (ESM::ItemLevList).name()) { @@ -300,7 +298,6 @@ void MWWorld::ContainerStore::addInitialItem (const std::string& id, const std:: } else { - ref.getPtr().getRefData().setCount (count); ref.getPtr().getCellRef().mOwner = owner; addImp (ref.getPtr()); } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index affcf03878..e1a2fb096d 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1949,7 +1949,6 @@ namespace MWWorld if(werewolf) { ManualRef ref(getStore(), "WerewolfRobe"); - ref.getPtr().getRefData().setCount(1); // Configure item's script variables std::string script = Class::get(ref.getPtr()).getScript(ref.getPtr()); From 26e4ccb8c20218c8848279894a7fcbddc5898f5a Mon Sep 17 00:00:00 2001 From: Emanuel Guevel Date: Mon, 21 Oct 2013 00:33:47 +0200 Subject: [PATCH 209/434] Cosmetic changes Rename ContainerStore::addImpl to addNewStack (it was confusing, since ContainerStore had methods named 'addImp' and 'addImpl'). --- apps/openmw/mwworld/containerstore.cpp | 10 +++++----- apps/openmw/mwworld/containerstore.hpp | 4 ++-- apps/openmw/mwworld/inventorystore.cpp | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index d25b29397a..07616c8672 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -141,19 +141,19 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp (const Ptr& ptr) || Misc::StringUtils::ciEqual(ptr.getCellRef().mRefID, "gold_100")) { int count = MWWorld::Class::get(ptr).getValue(ptr) * ptr.getRefData().getCount(); - MWWorld::ManualRef ref(esmStore, "Gold_001", count); for (MWWorld::ContainerStoreIterator iter (begin(type)); iter!=end(); ++iter) { if (Misc::StringUtils::ciEqual((*iter).get()->mRef.mRefID, "gold_001")) { - (*iter).getRefData().setCount( (*iter).getRefData().getCount() + count); + iter->getRefData().setCount(iter->getRefData().getCount() + count); flagAsModified(); return iter; } } - return addImpl(ref.getPtr()); + MWWorld::ManualRef ref(esmStore, "Gold_001", count); + return addNewStack(ref.getPtr()); } // determine whether to stack or not @@ -169,10 +169,10 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp (const Ptr& ptr) } } // if we got here, this means no stacking - return addImpl(ptr); + return addNewStack(ptr); } -MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImpl (const Ptr& ptr) +MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addNewStack (const Ptr& ptr) { ContainerStoreIterator it = begin(); diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index d19f1ad50f..ca6609ecf2 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -86,8 +86,8 @@ namespace MWWorld /// @return the number of items actually removed protected: - ContainerStoreIterator addImpl (const Ptr& ptr); - ///< Add the item to this container (no stacking) + ContainerStoreIterator addNewStack (const Ptr& ptr); + ///< Add the item to this container (do not try to stack it onto existing items) public: diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 505153a661..162c056d29 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -107,7 +107,7 @@ void MWWorld::InventoryStore::equip (int slot, const ContainerStoreIterator& ite // add the item again with a count of count-1, then set the count of the original (that will be equipped) to 1 int count = iterator->getRefData().getCount(); iterator->getRefData().setCount(count-1); - addImpl(*iterator); + addNewStack(*iterator); iterator->getRefData().setCount(1); } @@ -218,7 +218,7 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& npc) // add the item again with a count of count-1, then set the count of the original (that will be equipped) to 1 int count = iter->getRefData().getCount(); iter->getRefData().setCount(count-1); - addImpl(*iter); + addNewStack(*iter); iter->getRefData().setCount(1); } } From 750f1fd760f486b8937275d2974f2aa397d65d2c Mon Sep 17 00:00:00 2001 From: Emanuel Guevel Date: Wed, 23 Oct 2013 14:36:55 +0200 Subject: [PATCH 210/434] Edit ContainerStore::stacks for clarifications and correctness Rename arguments and fix some potential errors (add checks). --- apps/openmw/mwworld/containerstore.cpp | 37 ++++++++++++++++---------- apps/openmw/mwworld/containerstore.hpp | 3 ++- apps/openmw/mwworld/inventorystore.cpp | 16 ++++++----- apps/openmw/mwworld/inventorystore.hpp | 4 +-- 4 files changed, 36 insertions(+), 24 deletions(-) diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index 07616c8672..c041beffa6 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -77,22 +77,31 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::end() return ContainerStoreIterator (this); } -bool MWWorld::ContainerStore::stacks(const Ptr& ptr1, const Ptr& ptr2) +bool MWWorld::ContainerStore::stacks(const Ptr& stack, const Ptr& item) { - /// \todo add current enchantment charge here when it is implemented - if ( Misc::StringUtils::ciEqual(ptr1.getCellRef().mRefID, ptr2.getCellRef().mRefID) - && MWWorld::Class::get(ptr1).getScript(ptr1) == "" // item with a script never stacks - && MWWorld::Class::get(ptr1).getEnchantment(ptr1) == "" // item with enchantment never stacks (we could revisit this later, but for now it makes selecting items in the spell window much easier) - && ptr1.getCellRef().mOwner == ptr2.getCellRef().mOwner - && ptr1.getCellRef().mSoul == ptr2.getCellRef().mSoul - // item that is already partly used up never stacks - && (!MWWorld::Class::get(ptr1).hasItemHealth(ptr1) || ptr1.getCellRef().mCharge == -1 - || MWWorld::Class::get(ptr1).getItemMaxHealth(ptr1) == ptr1.getCellRef().mCharge) - && (!MWWorld::Class::get(ptr2).hasItemHealth(ptr2) || ptr2.getCellRef().mCharge == -1 - || MWWorld::Class::get(ptr2).getItemMaxHealth(ptr2) == ptr2.getCellRef().mCharge)) - return true; + const MWWorld::Class& cls1 = MWWorld::Class::get(stack); + const MWWorld::Class& cls2 = MWWorld::Class::get(item); - return false; + /// \todo add current enchantment charge here when it is implemented + return stack != item // an item never stacks onto itself + && Misc::StringUtils::ciEqual(stack.getCellRef().mRefID, item.getCellRef().mRefID) + && stack.getCellRef().mOwner == item.getCellRef().mOwner + && stack.getCellRef().mSoul == item.getCellRef().mSoul + + // item with a script never stacks + && cls1.getScript(stack) == "" + && cls2.getScript(item) == "" + + // item with enchantment never stacks (we could revisit this later, + // but for now it makes selecting items in the spell window much easier) + && cls1.getEnchantment(stack) == "" + && cls2.getEnchantment(item) == "" + + // item that is already partly used up never stacks + && (!cls1.hasItemHealth(stack) || stack.getCellRef().mCharge == -1 + || cls1.getItemMaxHealth(stack) == stack.getCellRef().mCharge) + && (!cls2.hasItemHealth(item) || item.getCellRef().mCharge == -1 + || cls2.getItemMaxHealth(item) == item.getCellRef().mCharge); } MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr, const Ptr& actorPtr) diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index ca6609ecf2..2cdefd2f4b 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -91,8 +91,9 @@ namespace MWWorld public: - virtual bool stacks (const Ptr& ptr1, const Ptr& ptr2); + virtual bool stacks (const Ptr& stack, const Ptr& item); ///< @return true if the two specified objects can stack with each other + /// @note stack is the item that is already in this container void fill (const ESM::InventoryList& items, const std::string& owner, const MWWorld::ESMStore& store); ///< Insert items into *this. diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 162c056d29..6057cfcf44 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -276,20 +276,22 @@ void MWWorld::InventoryStore::flagAsModified() mMagicEffectsUpToDate = false; } -bool MWWorld::InventoryStore::stacks(const Ptr& ptr1, const Ptr& ptr2) +bool MWWorld::InventoryStore::stacks(const Ptr& stack, const Ptr& item) { - bool canStack = MWWorld::ContainerStore::stacks(ptr1, ptr2); + bool canStack = MWWorld::ContainerStore::stacks(stack, item); if (!canStack) return false; - // don't stack if the item being checked against is currently equipped. + // don't stack if 'stack' (the item being checked against) is currently equipped. for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter) { - if (*iter != end() && ptr1 == **iter) - return false; - if (*iter != end() && ptr2 == **iter) - return false; + if (*iter != end() && stack == **iter) + { + bool stackWhenEquipped = MWWorld::Class::get(**iter).getEquipmentSlots(**iter).second; + if (!stackWhenEquipped) + return false; + } } return true; diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index 9468f928f1..26ea90c3d6 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -104,9 +104,9 @@ namespace MWWorld ///< \attention This function is internal to the world model and should not be called from /// outside. - virtual bool stacks (const Ptr& ptr1, const Ptr& ptr2); + virtual bool stacks (const Ptr& stack, const Ptr& item); ///< @return true if the two specified objects can stack with each other - /// @note ptr1 is the item that is already in this container + /// @note stack is the item that is already in this container (it may be equipped) virtual int remove(const Ptr& item, int count, const Ptr& actor); ///< Remove \a count item(s) designated by \a item from this inventory. From 59c963b6cc9f21a3486dafe2b1470e4a7ff7e383 Mon Sep 17 00:00:00 2001 From: Emanuel Guevel Date: Thu, 24 Oct 2013 02:14:05 +0200 Subject: [PATCH 211/434] Auto-equip items when a clothe or an armor is removed from inventory This fix auto-equip on corpses. --- apps/openmw/mwworld/inventorystore.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 6057cfcf44..3571f64baa 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -321,7 +321,19 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor } } - return ContainerStore::remove(item, count, actor); + int retCount = ContainerStore::remove(item, count, actor); + + // If an armor/clothing item is removed, try to find a replacement, + // but not for the player nor werewolves. + if ((actor.getRefData().getHandle() != "player") + && !(MWWorld::Class::get(actor).getNpcStats(actor).isWerewolf())) + { + std::string type = item.getTypeName(); + if ((type == typeid(ESM::Armor).name()) || (type == typeid(ESM::Clothing).name())) + autoEquip(actor); + } + + return retCount; } void MWWorld::InventoryStore::unequipSlot(int slot, const MWWorld::Ptr& actor) From 12dbbde1e37898781bf94198b945eaf65e9c0cd9 Mon Sep 17 00:00:00 2001 From: Emanuel Guevel Date: Fri, 1 Nov 2013 01:01:55 +0100 Subject: [PATCH 212/434] InvStore::unequipSlot: return an iterator to the unequipped item --- apps/openmw/mwworld/inventorystore.cpp | 15 +++++++++++---- apps/openmw/mwworld/inventorystore.hpp | 7 ++++++- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 3571f64baa..4d7fa2d64d 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -336,18 +336,21 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor return retCount; } -void MWWorld::InventoryStore::unequipSlot(int slot, const MWWorld::Ptr& actor) +MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, const MWWorld::Ptr& actor) { ContainerStoreIterator it = getSlot(slot); + if (it != end()) { // restack item previously in this slot + ContainerStoreIterator retval = it; for (MWWorld::ContainerStoreIterator iter (begin()); iter != end(); ++iter) { - if (stacks(*iter, *mSlots[slot])) + if (stacks(*iter, *it)) { - iter->getRefData().setCount(iter->getRefData().getCount() + mSlots[slot]->getRefData().getCount()); - mSlots[slot]->getRefData().setCount(0); + iter->getRefData().setCount(iter->getRefData().getCount() + it->getRefData().getCount()); + it->getRefData().setCount(0); + retval = iter; break; } } @@ -377,5 +380,9 @@ void MWWorld::InventoryStore::unequipSlot(int slot, const MWWorld::Ptr& actor) } /// \todo update actor model + + return retval; } + + return it; } diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index 26ea90c3d6..1b11d9dcea 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -113,7 +113,12 @@ namespace MWWorld /// /// @return the number of items actually removed - void unequipSlot(int slot, const Ptr& actor); + ContainerStoreIterator unequipSlot(int slot, const Ptr& actor); + ///< Unequip \a slot. + /// + /// @return an iterator to the item that was previously in the slot + /// (it can be re-stacked so its count may be different than when it + /// was equipped). }; } From 37e91a278e1390c47488f28e14ff16d8e47609ef Mon Sep 17 00:00:00 2001 From: Emanuel Guevel Date: Fri, 1 Nov 2013 00:21:15 +0100 Subject: [PATCH 213/434] Add InventoryStore::unequipItem() --- apps/openmw/mwworld/inventorystore.cpp | 12 ++++++++++++ apps/openmw/mwworld/inventorystore.hpp | 8 ++++++++ 2 files changed, 20 insertions(+) diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 4d7fa2d64d..4291c120f3 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -386,3 +386,15 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, c return it; } + +MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipItem(const MWWorld::Ptr& item, const MWWorld::Ptr& actor) +{ + for (int slot=0; slot Date: Fri, 1 Nov 2013 00:20:33 +0100 Subject: [PATCH 214/434] InventoryWindow: call InventoryStore::unequipItem() when an equipped item is dragged The unequipped item is also re-stacked if needed. --- apps/openmw/mwgui/inventorywindow.cpp | 53 +++++++++++++++------------ apps/openmw/mwgui/inventorywindow.hpp | 1 - 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 5c8cd2cdc4..0ae633aa05 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -145,10 +145,38 @@ namespace MWGui const ItemStack& item = mTradeModel->getItem(index); - unequipItem(item.mBase); - MWWorld::Ptr object = item.mBase; int count = item.mCount; + + if (item.mType == ItemStack::Type_Equipped) + { + MWWorld::InventoryStore& invStore = MWWorld::Class::get(mPtr).getInventoryStore(mPtr); + MWWorld::Ptr newStack = *invStore.unequipItem(item.mBase, mPtr); + + // The unequipped item was re-stacked. We have to update the index + // since the item pointed does not exist anymore. + if (item.mBase != newStack) + { + // newIndex will store the index of the ItemStack the item was stacked on + int newIndex = -1; + for (size_t i=0; i < mTradeModel->getItemCount(); ++i) + { + if (mTradeModel->getItem(i).mBase == newStack) + { + newIndex = i; + break; + } + } + + if (newIndex == -1) + throw std::runtime_error("Can't find restacked item"); + + index = newIndex; + object = mTradeModel->getItem(index).mBase; + } + + } + bool shift = MyGUI::InputManager::getInstance().isShiftPressed(); if (MyGUI::InputManager::getInstance().isControlPressed()) count = 1; @@ -375,27 +403,6 @@ namespace MWGui return MWWorld::Ptr(); } - void InventoryWindow::unequipItem(const MWWorld::Ptr& item) - { - MWWorld::InventoryStore& invStore = MWWorld::Class::get(mPtr).getInventoryStore(mPtr); - - for (int slot=0; slot < MWWorld::InventoryStore::Slots; ++slot) - { - MWWorld::ContainerStoreIterator it = invStore.getSlot(slot); - if (it != invStore.end() && *it == item) - { - invStore.equip(slot, invStore.end(), mPtr); - std::string script = MWWorld::Class::get(*it).getScript(*it); - - // Unset OnPCEquip Variable on item's script, if it has a script with that variable declared - if(script != "") - (*it).getRefData().getLocals().setVarByInt(script, "onpcequip", 0); - - return; - } - } - } - void InventoryWindow::updateEncumbranceBar() { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); diff --git a/apps/openmw/mwgui/inventorywindow.hpp b/apps/openmw/mwgui/inventorywindow.hpp index 35140437d1..78d00fd1e7 100644 --- a/apps/openmw/mwgui/inventorywindow.hpp +++ b/apps/openmw/mwgui/inventorywindow.hpp @@ -103,7 +103,6 @@ namespace MWGui void onAvatarClicked(MyGUI::Widget* _sender); void onPinToggled(); - void unequipItem(const MWWorld::Ptr& item); void updateEncumbranceBar(); void notifyContentChanged(); }; From f428921b93e3d4ecc7f82291aa07f5cb5e18665d Mon Sep 17 00:00:00 2001 From: Emanuel Guevel Date: Thu, 24 Oct 2013 10:48:10 +0200 Subject: [PATCH 215/434] Always update the source container view on drag&drop This fix the indicator not being displayed for items auto-equipped after an other item is removed. --- apps/openmw/mwgui/container.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index bc869e5fef..5d864752fc 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -100,6 +100,9 @@ namespace MWGui finish(); targetView->update(); + + // We need to update the view since an other item could be auto-equipped. + mSourceView->update(); } void DragAndDrop::finish() From 467bd91651e9af7a8d3048df5ae1847dede680a8 Mon Sep 17 00:00:00 2001 From: Emanuel Guevel Date: Fri, 25 Oct 2013 22:16:52 +0200 Subject: [PATCH 216/434] Update actor model on inventory change --- apps/openmw/mwbase/world.hpp | 2 ++ apps/openmw/mwrender/renderingmanager.cpp | 13 +++++++++++++ apps/openmw/mwrender/renderingmanager.hpp | 3 +++ apps/openmw/mwworld/inventorystore.cpp | 17 ++++++++++++++++- apps/openmw/mwworld/inventorystore.hpp | 3 +++ apps/openmw/mwworld/worldimp.cpp | 5 +++++ apps/openmw/mwworld/worldimp.hpp | 2 ++ 7 files changed, 44 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 4eb9d30880..ddf8417448 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -413,6 +413,8 @@ namespace MWBase virtual bool toggleGodMode() = 0; virtual void castSpell (const MWWorld::Ptr& actor) = 0; + + virtual void updateAnimParts(const MWWorld::Ptr& ptr) = 0; }; } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 93425191dd..817a2d236d 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -327,6 +327,19 @@ void RenderingManager::rebuildPtr(const MWWorld::Ptr &ptr) } } +void RenderingManager::updateAnimParts(const MWWorld::Ptr& ptr) +{ + NpcAnimation *anim = NULL; + + if(ptr.getRefData().getHandle() == "player") + anim = mPlayerAnimation; + else if(MWWorld::Class::get(ptr).isActor()) + anim = dynamic_cast(mActors.getAnimation(ptr)); + + if(anim) + anim->updateParts(); +} + void RenderingManager::update (float duration, bool paused) { MWBase::World *world = MWBase::Environment::get().getWorld(); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 2d08139128..8913e0a789 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -141,6 +141,9 @@ public: /// and equipment. void rebuildPtr(const MWWorld::Ptr &ptr); + /// Update actor model parts. + void updateAnimParts(const MWWorld::Ptr &ptr); + void update (float duration, bool paused); void setAmbientColour(const Ogre::ColourValue& colour); diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 4291c120f3..dbb5a80b54 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -41,6 +41,7 @@ void MWWorld::InventoryStore::initSlots (TSlots& slots) MWWorld::InventoryStore::InventoryStore() : mMagicEffectsUpToDate (false) , mSelectedEnchantItem(end()) + , mActorModelUpdateEnabled (true) { initSlots (mSlots); } @@ -114,6 +115,8 @@ void MWWorld::InventoryStore::equip (int slot, const ContainerStoreIterator& ite mSlots[slot] = iterator; flagAsModified(); + + updateActorModel(actor); } void MWWorld::InventoryStore::unequipAll(const MWWorld::Ptr& actor) @@ -148,6 +151,9 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& npc) TSlots slots; initSlots (slots); + // Disable model update during auto-equip + mActorModelUpdateEnabled = false; + for (ContainerStoreIterator iter (begin()); iter!=end(); ++iter) { Ptr test = *iter; @@ -236,9 +242,12 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& npc) changed = true; } + mActorModelUpdateEnabled = true; + if (changed) { mSlots.swap (slots); + updateActorModel(npc); flagAsModified(); } } @@ -379,7 +388,7 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, c } } - /// \todo update actor model + updateActorModel(actor); return retval; } @@ -398,3 +407,9 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipItem(const MWWor throw std::runtime_error ("attempt to unequip an item that is not currently equipped"); } + +void MWWorld::InventoryStore::updateActorModel(const MWWorld::Ptr& actor) +{ + if (mActorModelUpdateEnabled) + MWBase::Environment::get().getWorld()->updateAnimParts(actor); +} diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index 199c422435..02d65d54a0 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -45,6 +45,7 @@ namespace MWWorld mutable MWMechanics::MagicEffects mMagicEffects; mutable bool mMagicEffectsUpToDate; + bool mActorModelUpdateEnabled; typedef std::vector TSlots; @@ -57,6 +58,8 @@ namespace MWWorld void initSlots (TSlots& slots); + void updateActorModel (const Ptr& actor); + public: InventoryStore(); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index e1a2fb096d..be71bebeee 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2135,4 +2135,9 @@ namespace MWWorld // TODO: RT_Range, RT_Touch } } + + void World::updateAnimParts(const Ptr& actor) + { + mRendering->updateAnimParts(actor); + } } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index cd982aceee..602d8d5e74 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -473,6 +473,8 @@ namespace MWWorld virtual bool toggleGodMode(); virtual void castSpell (const MWWorld::Ptr& actor); + + virtual void updateAnimParts(const MWWorld::Ptr& ptr); }; } From d2dcf0b2036f03d3f42ad47e5d329297724fa22f Mon Sep 17 00:00:00 2001 From: Emanuel Guevel Date: Sat, 2 Nov 2013 13:06:15 +0100 Subject: [PATCH 217/434] Add a warning comment to RefData::setCount() --- apps/openmw/mwworld/refdata.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/openmw/mwworld/refdata.hpp b/apps/openmw/mwworld/refdata.hpp index 642f5412c8..d5701efc51 100644 --- a/apps/openmw/mwworld/refdata.hpp +++ b/apps/openmw/mwworld/refdata.hpp @@ -73,6 +73,11 @@ namespace MWWorld void setLocals (const ESM::Script& script); void setCount (int count); + /// Set object count (an object pile is a simple object with a count >1). + /// + /// \warning Do not call setCount() to add or remove objects from a + /// container or an actor's inventory. Call ContainerStore::add() or + /// ContainerStore::remove() instead. MWScript::Locals& getLocals(); From bbfd7f4c9d0d53c2be676a1591e81e626e974180 Mon Sep 17 00:00:00 2001 From: Emanuel Guevel Date: Sat, 9 Nov 2013 02:47:11 +0100 Subject: [PATCH 218/434] Disable equipped item re-stacking when the item is removed from inventory The item was not removed if it was re-stacked. --- apps/openmw/mwworld/inventorystore.cpp | 24 ++++++++++++++---------- apps/openmw/mwworld/inventorystore.hpp | 6 +++--- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index dbb5a80b54..dc2d0cab13 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -325,7 +325,8 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor if (*mSlots[slot] == item) { - unequipSlot(slot, actor); + // restacking is disabled cause it may break removal + unequipSlot(slot, actor, false); break; } } @@ -345,22 +346,25 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor return retCount; } -MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, const MWWorld::Ptr& actor) +MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, const MWWorld::Ptr& actor, bool restack) { ContainerStoreIterator it = getSlot(slot); if (it != end()) { - // restack item previously in this slot ContainerStoreIterator retval = it; - for (MWWorld::ContainerStoreIterator iter (begin()); iter != end(); ++iter) - { - if (stacks(*iter, *it)) + + if (restack) { + // restack item previously in this slot + for (MWWorld::ContainerStoreIterator iter (begin()); iter != end(); ++iter) { - iter->getRefData().setCount(iter->getRefData().getCount() + it->getRefData().getCount()); - it->getRefData().setCount(0); - retval = iter; - break; + if (stacks(*iter, *it)) + { + iter->getRefData().setCount(iter->getRefData().getCount() + it->getRefData().getCount()); + it->getRefData().setCount(0); + retval = iter; + break; + } } } diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index 02d65d54a0..c4ad517776 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -116,12 +116,12 @@ namespace MWWorld /// /// @return the number of items actually removed - ContainerStoreIterator unequipSlot(int slot, const Ptr& actor); + ContainerStoreIterator unequipSlot(int slot, const Ptr& actor, bool restack = true); ///< Unequip \a slot. /// /// @return an iterator to the item that was previously in the slot - /// (it can be re-stacked so its count may be different than when it - /// was equipped). + /// (if \a restack is true, the item can be re-stacked so its count + /// may differ from when it was equipped). ContainerStoreIterator unequipItem(const Ptr& item, const Ptr& actor); ///< Unequip an item identified by its Ptr. An exception is thrown From ec6018928c32771cc59ee1b511cc41df0e45a8ba Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 10 Nov 2013 12:09:49 +0100 Subject: [PATCH 219/434] some fixes to info record sorting (doesn't address the main problem) --- apps/opencs/model/world/infocollection.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/apps/opencs/model/world/infocollection.cpp b/apps/opencs/model/world/infocollection.cpp index d326543fbf..8e7b6cf01b 100644 --- a/apps/opencs/model/world/infocollection.cpp +++ b/apps/opencs/model/world/infocollection.cpp @@ -38,7 +38,22 @@ void CSMWorld::InfoCollection::load (const Info& record, bool base) } if (index==-1) - index = getIdMap().size(); + { + std::pair range = getTopicRange (topic); + + if (range.first==range.second) + index = getIdMap().size(); + else + { + for (; range.first!=range.second; ++range.first) + { + if (range.first->second>index) + index = range.first->second; + } + + ++index; + } + } insertRecord (record2, index); } From 583f1ae9c285f67646691402fcd07f09573c0ad1 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 10 Nov 2013 13:00:46 +0100 Subject: [PATCH 220/434] fixed info record ordering and performance problems by determining topic ranges in the record collection instead of in the index collection --- apps/opencs/model/world/collection.hpp | 8 +++ apps/opencs/model/world/infocollection.cpp | 64 +++++++++++----------- apps/opencs/model/world/infocollection.hpp | 6 +- 3 files changed, 42 insertions(+), 36 deletions(-) diff --git a/apps/opencs/model/world/collection.hpp b/apps/opencs/model/world/collection.hpp index 1eee36ae97..316e76c5f1 100644 --- a/apps/opencs/model/world/collection.hpp +++ b/apps/opencs/model/world/collection.hpp @@ -55,6 +55,8 @@ namespace CSMWorld const std::map& getIdMap() const; + const std::vector >& getRecords() const; + public: Collection(); @@ -138,6 +140,12 @@ namespace CSMWorld return mIndex; } + template + const std::vector >& Collection::getRecords() const + { + return mRecords; + } + template Collection::Collection() {} diff --git a/apps/opencs/model/world/infocollection.cpp b/apps/opencs/model/world/infocollection.cpp index 8e7b6cf01b..b5c1872a11 100644 --- a/apps/opencs/model/world/infocollection.cpp +++ b/apps/opencs/model/world/infocollection.cpp @@ -39,20 +39,9 @@ void CSMWorld::InfoCollection::load (const Info& record, bool base) if (index==-1) { - std::pair range = getTopicRange (topic); + Range range = getTopicRange (topic); - if (range.first==range.second) - index = getIdMap().size(); - else - { - for (; range.first!=range.second; ++range.first) - { - if (range.first->second>index) - index = range.first->second; - } - - ++index; - } + index = std::distance (getRecords().begin(), range.second); } insertRecord (record2, index); @@ -75,11 +64,11 @@ int CSMWorld::InfoCollection::getIndex (const std::string& id, const std::string { std::string fullId = Misc::StringUtils::lowerCase (topic) + "#" + id; - std::pair range = getTopicRange (topic); + std::pair range = getTopicRange (topic); for (; range.first!=range.second; ++range.first) - if (range.first->first==fullId) - return std::distance (getIdMap().begin(), range.first); + if (Misc::StringUtils::lowerCase (range.first->get().mId)==fullId) + return std::distance (getRecords().begin(), range.first); return -1; } @@ -91,18 +80,12 @@ int CSMWorld::InfoCollection::getAppendIndex (const std::string& id, UniversalId if (separator==std::string::npos) throw std::runtime_error ("invalid info ID: " + id); - std::pair range = getTopicRange (id.substr (0, separator)); + std::pair range = getTopicRange (id.substr (0, separator)); if (range.first==range.second) return Collection >::getAppendIndex (id, type); - int index = 0; - - for (; range.first!=range.second; ++range.first) - if (range.first->second>index) - index = range.first->second; - - return index+1; + return std::distance (getRecords().begin(), range.second); } void CSMWorld::InfoCollection::load (ESM::ESMReader& reader, bool base, const ESM::Dialogue& dialogue) @@ -146,25 +129,40 @@ void CSMWorld::InfoCollection::load (ESM::ESMReader& reader, bool base, const ES } } -std::pair - CSMWorld::InfoCollection::getTopicRange (const std::string& topic) const +CSMWorld::InfoCollection::Range CSMWorld::InfoCollection::getTopicRange (const std::string& topic) + const { std::string topic2 = Misc::StringUtils::lowerCase (topic); - MapConstIterator begin = getIdMap().lower_bound (topic2); + std::map::const_iterator iter = getIdMap().lower_bound (topic2); // Skip invalid records: The beginning of a topic string could be identical to another topic // string. - for (; begin!=getIdMap().end(); ++begin) - if (Misc::StringUtils::lowerCase (getRecord (begin->second).get().mTopicId)==topic2) + for (; iter!=getIdMap().end(); ++iter) + { + std::string testTopicId = + Misc::StringUtils::lowerCase (getRecord (iter->second).get().mTopicId); + + if (testTopicId==topic2) break; + std::size_t size = topic2.size(); + + if (testTopicId.size()second; + // Find end - MapConstIterator end = begin; + RecordConstIterator end = begin; - for (; end!=getIdMap().end(); ++end) - if (Misc::StringUtils::lowerCase (getRecord (end->second).get().mTopicId)!=topic2) + for (; end!=getRecords().end(); ++end) + if (Misc::StringUtils::lowerCase (end->get().mTopicId)!=topic2) break; - return std::make_pair (begin, end); + return Range (begin, end); } \ No newline at end of file diff --git a/apps/opencs/model/world/infocollection.hpp b/apps/opencs/model/world/infocollection.hpp index 398f9becca..1bccbb4aee 100644 --- a/apps/opencs/model/world/infocollection.hpp +++ b/apps/opencs/model/world/infocollection.hpp @@ -15,7 +15,8 @@ namespace CSMWorld { public: - typedef std::map::const_iterator MapConstIterator; + typedef std::vector >::const_iterator RecordConstIterator; + typedef std::pair Range; private: @@ -34,8 +35,7 @@ namespace CSMWorld void load (ESM::ESMReader& reader, bool base, const ESM::Dialogue& dialogue); - std::pair getTopicRange (const std::string& topic) - const; + Range getTopicRange (const std::string& topic) const; ///< Return iterators that point to the beginning and past the end of the range for /// the given topic. }; From 71828351e679354a01deeaf81824b820a39318b7 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 10 Nov 2013 15:28:26 +0100 Subject: [PATCH 221/434] Code cleanup: use enum instead of integer --- apps/openmw/mwmechanics/actors.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 39276e9645..32f3b4bf96 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -164,11 +164,13 @@ namespace MWMechanics for(int i = 0;i < 3;++i) { DynamicStat stat = creatureStats.getDynamic(i); - stat.setModifier(effects.get(EffectKey(80+i)).mMagnitude - - effects.get(EffectKey(18+i)).mMagnitude); + stat.setModifier(effects.get(EffectKey(ESM::MagicEffect::FortifyHealth+i)).mMagnitude - + effects.get(EffectKey(ESM::MagicEffect::DrainHealth+i)).mMagnitude); creatureStats.setDynamic(i, stat); } + + } void Actors::calculateNpcStatModifiers (const MWWorld::Ptr& ptr) From 6cd373c5c6ffe4d9e4b172d4847a634534778ebf Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 10 Nov 2013 15:40:31 +0100 Subject: [PATCH 222/434] Only allow using powers once every 24 h. Apply on touch effects. --- apps/openmw/mwmechanics/alchemy.cpp | 2 +- apps/openmw/mwmechanics/creaturestats.cpp | 14 +++++++ apps/openmw/mwmechanics/creaturestats.hpp | 5 +++ apps/openmw/mwworld/worldimp.cpp | 51 +++++++++++++++++------ 4 files changed, 58 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index adfd6d5fce..c92b2c0403 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -273,7 +273,7 @@ void MWMechanics::Alchemy::addPotion (const std::string& name) newRecord.mName = name; - int index = static_cast (std::rand()/static_cast (RAND_MAX+1)*6); + int index = static_cast (std::rand()/(static_cast (RAND_MAX)+1)*6); assert (index>=0 && index<6); static const char *name[] = { "standard", "bargain", "cheap", "fresh", "exclusive", "quality" }; diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 52ed43be37..c0450c3443 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -342,4 +342,18 @@ namespace MWMechanics { return mLastHitObject; } + + bool CreatureStats::canUsePower(const std::string &power) + { + std::map::iterator it = mUsedPowers.find(power); + if (it == mUsedPowers.end() || it->second + 24 <= MWBase::Environment::get().getWorld()->getTimeStamp()) + return true; + else + return false; + } + + void CreatureStats::usePower(const std::string &power) + { + mUsedPowers[power] = MWBase::Environment::get().getWorld()->getTimeStamp(); + } } diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index 1a7cb5d698..989f1d48c5 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -40,6 +40,8 @@ namespace MWMechanics std::string mLastHitObject; // The last object to hit this actor + std::map mUsedPowers; + protected: bool mIsWerewolf; Stat mWerewolfAttributes[8]; @@ -47,6 +49,9 @@ namespace MWMechanics public: CreatureStats(); + bool canUsePower (const std::string& power); + void usePower (const std::string& power); + const Stat & getAttribute(int index) const; const DynamicStat & getHealth() const; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 6c18ec4fd6..a352c0e11b 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1991,6 +1991,7 @@ namespace MWWorld stats.setAttackingOrSpell(false); std::string selectedSpell = stats.getSpells().getSelectedSpell(); + std::string sourceName = ""; if (!selectedSpell.empty()) { const ESM::Spell* spell = getStore().get().search(selectedSpell); @@ -2011,6 +2012,18 @@ namespace MWWorld stats.setMagicka(magicka); } + // If this is a power, check if it was already used in last 24h + if (!fail && spell->mData.mType & ESM::Spell::ST_Power) + { + if (stats.canUsePower(selectedSpell)) + stats.usePower(selectedSpell); + else + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sPowerAlreadyUsed}"); + fail = true; + } + } + // Check success int successChance = MWMechanics::getSpellSuccessChance(selectedSpell, actor); int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] @@ -2042,18 +2055,13 @@ namespace MWWorld actor.getClass().skillUsageSucceeded(actor, MWMechanics::spellSchoolToSkill(MWMechanics::getSpellSchool(selectedSpell, actor)), 0); - actor.getClass().getCreatureStats(actor).getActiveSpells().addSpell(selectedSpell, actor, ESM::RT_Self); - // TODO: RT_Range, RT_Touch - return; } - InventoryStore& inv = actor.getClass().getInventoryStore(actor); - if (inv.getSelectedEnchantItem() != inv.end()) + if (selectedSpell.empty() && inv.getSelectedEnchantItem() != inv.end()) { MWWorld::Ptr item = *inv.getSelectedEnchantItem(); - std::string id = item.getClass().getEnchantment(item); - const ESM::Enchantment* enchantment = getStore().get().search (id); - + selectedSpell = item.getClass().getEnchantment(item); + const ESM::Enchantment* enchantment = getStore().get().search (selectedSpell); if (enchantment->mData.mType == ESM::Enchantment::WhenUsed) { @@ -2080,8 +2088,7 @@ namespace MWWorld item.getRefData().setCount(item.getRefData().getCount()-1); } - std::string itemName = item.getClass().getName(item); - actor.getClass().getCreatureStats(actor).getActiveSpells().addSpell(id, actor, ESM::RT_Self, itemName); + sourceName = item.getClass().getName(item); if (!item.getRefData().getCount()) { @@ -2091,10 +2098,28 @@ namespace MWWorld } else MWBase::Environment::get().getWindowManager()->setSelectedEnchantItem(item); // Set again to show the modified charge - - - // TODO: RT_Range, RT_Touch } + + // Now apply the spell! + + // Apply Self portion + actor.getClass().getCreatureStats(actor).getActiveSpells().addSpell(selectedSpell, actor, ESM::RT_Self, sourceName); + + // Apply Touch portion + // TODO: Distance is probably incorrect, and should it be hardcoded? + std::pair contact = getHitContact(actor, 100); + if (!contact.first.isEmpty()) + { + if (contact.first.getClass().isActor()) + contact.first.getClass().getCreatureStats(contact.first).getActiveSpells().addSpell(selectedSpell, contact.first, ESM::RT_Touch, sourceName); + else + { + // We hit a non-actor, e.g. a door. Only instant effects are relevant. + // inflictSpellOnNonActor(contact.first, selectedSpell, ESM::RT_Touch); + } + } + + // TODO: Launch projectile if there's a Target portion } } From 04edd25add7ed4cc43e7f705a8d04e17cb9e6514 Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Sun, 10 Nov 2013 19:09:05 +0400 Subject: [PATCH 223/434] OpenCS as a separate app bundle --- apps/opencs/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 7498044ab5..1fb5737a3f 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -149,12 +149,17 @@ qt4_add_resources(OPENCS_RES_SRC ${OPENCS_RES}) include_directories(${CMAKE_CURRENT_BINARY_DIR}) add_executable(opencs + MACOSX_BUNDLE ${OPENCS_SRC} ${OPENCS_UI_HDR} ${OPENCS_MOC_SRC} ${OPENCS_RES_SRC} ) +if(APPLE) + set_target_properties(opencs PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${OpenMW_BINARY_DIR}") +endif(APPLE) + target_link_libraries(opencs ${Boost_LIBRARIES} ${QT_LIBRARIES} From df5b52b45b4601857f09cd4833e34b285dcbdec0 Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Sun, 10 Nov 2013 20:11:53 +0400 Subject: [PATCH 224/434] OpenCS.app icon & bundle properties --- apps/opencs/CMakeLists.txt | 20 +++++++++++++++++++- files/mac/opencs.icns | Bin 0 -> 50537 bytes 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 files/mac/opencs.icns diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 1fb5737a3f..bd8a852b92 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -148,16 +148,34 @@ qt4_add_resources(OPENCS_RES_SRC ${OPENCS_RES}) include_directories(${CMAKE_CURRENT_BINARY_DIR}) +if(APPLE) + set (OPENCS_MAC_ICON ${CMAKE_SOURCE_DIR}/files/mac/opencs.icns) +else() + set (OPENCS_MAC_ICON "") +endif(APPLE) + add_executable(opencs MACOSX_BUNDLE ${OPENCS_SRC} ${OPENCS_UI_HDR} ${OPENCS_MOC_SRC} ${OPENCS_RES_SRC} + ${OPENCS_MAC_ICON} ) if(APPLE) - set_target_properties(opencs PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${OpenMW_BINARY_DIR}") + set_target_properties(opencs PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${OpenMW_BINARY_DIR}" + OUTPUT_NAME "OpenCS" + MACOSX_BUNDLE_ICON_FILE "opencs.icns" + MACOSX_BUNDLE_BUNDLE_NAME "OpenCS" + MACOSX_BUNDLE_GUI_IDENTIFIER "org.openmw.opencs" + MACOSX_BUNDLE_SHORT_VERSION_STRING ${OPENMW_VERSION} + MACOSX_BUNDLE_BUNDLE_VERSION ${OPENMW_VERSION} + ) + + set_source_files_properties(${OPENCS_MAC_ICON} PROPERTIES + MACOSX_PACKAGE_LOCATION Resources) endif(APPLE) target_link_libraries(opencs diff --git a/files/mac/opencs.icns b/files/mac/opencs.icns new file mode 100644 index 0000000000000000000000000000000000000000..98812f871d9a9a8bc7bfb1ab86d22cb33ab45bd5 GIT binary patch literal 50537 zcmeFZcX(6B_BJ|Tz+JK=%c_=ax%Y;AjaEgd{*9 zAqfEjBy=zx()6t@aFX+#bMEhZ?(^Jx|GAT}wfD@LHG5{(yfd@j*-lHyE=mp(K4pI45i)Nz*Qu(WjSL8{Vjan^I(JCxS9R9D; zm)@%mEfaAY(TbmMG{^flAgK%Y>dee@PlJYkK|}(ISwc}&x2mOF;Suh$?7-H%_T&b zGg^l8WNVNqcV_RxoSzX#A0??sAjB&Fo(Jt@tYPnKX=Ly18+YQ~6@+~CIWB>QzQc99 z?%yq)<0BbEhE;(GZ4jRO{tCPqgW|$Fyb&Rus1B_(!x<*!b9>NVRr#(q%pR(8?skQa zZVXD#f(!$D*CW(ge>o=&tIA;X%B?s0#%iLf#*(u@OS>xi{xdE`TG2c!vJ53xMB^nd zl;WW(N=7LTC@jSeSJfYdRYjY>agyt$iL32Q!>Z!9VaEd3hDD~LC>B(On*Xf`q19}P zw!4X*wX0j$;k!^3OQxccyS7J1+5FFUGq9?#6tq)BsHg1QHLNN`oL$-ep{1%wXhf%e zsLCAHD;M@$;&) zt||$KLv9qP%F6IBpeiaPJi}aFMc2&OWygIvi+zh~~LXx4{%%PI7 zBNw46Qt~ot;p5Pm+!J>-0+Lhv5Eb?- z^sVzHPS0NPGv|rub8U9C=y%cgKDA8fOOYrt_vzbv8#cccT_K~DmqqV5D{nv6L&){* ziyy0`xGKQ>^`pZpvyFL6Qi$7*!)g(E^yqEDA2R4o$K z=`H^8?#QX%Z{3MNi1L%@zIUkAgWn$iVNAk9E~jUr({kw3P{vtxObPVs8PWINtYv7` zvJcN_rEBA`&p#{pF&_I_8uouhNy42S{Cfvru!xYvty&~`;vB;B|3vH>(E^(-T4dvE zZaLu5Ee9ORU4uiVYu2FZHPxsTdltt_B9J-#j@==;hHn%h{0SbCyp;gD9K|}(@1UOr z|Nc|>|ET;W*qcKuxN}{R+&(0E;Mksx<*~sIJxKEN`%li7uQ6c4E$_2uq63x;PA`({ zI&kwrJCkXQP-T4AEdvuI}iZ+>o-y z(8Ml!YF|%vRtc{Ll6walHI-D+=!@U>w5O0)$q^JObffy7_*8+2j~Kj?_TEq>mLV!V za(yILVx^3{oE(9sZW0v~54rJaHkl|u zD~&zNZr_`SCgZNv<0$JnA!P1vsjEU!(KRwNGd2yn^xGeIw?mT!I2voPbe+PGm%9qf z({rtdufLCX+U2{)uDv-3$;oStIcy!*5M*birytzeS6x~=dUWy5MpQG)t3_mYV-=RB zU2r(ilGr8e%UFB}`V{^t*kNJ?a?tf&=|%`X5G zn3=DH>&_P!n~=OngGSPeZRl!4Xrv_>qD%J-wIC@?rlLBxeb*IHACh)Zp(yIbY&?FT zs1HeTHE4LMO+uu@dGrtC+_isUGyGi6C*#(CzUXs5nB2{X=J-`%Mv z;yDNvS7N0k*5qw$tn<)s0P)IVX+mW8br@)>I{%0vQg>&Z#u)lHl@KfDJllR z-jSGX#uZlcHE~LSnAtt$M6?`iQ&quR7tQkgq4 zS~*}YwoP59f-T#&O(Nv&W3R5DK%XnjsT^^_VYX=%C_IAMrmvHQsse#&47OPtM-tnn zeaN=+V4H!q1}q|pWeBzz6r-uA0Jf>SHs{9Am~A@i(-nyf1F%g`Gc7h*SwqiQUq{#D z!u#KDW47sFz@iZ820qBeQJLxDY~$qa;o=zk<$T z<2%eYIRsg{OI#&po8#k!aNWB2C1#scyo@SW&crm~HAR z;bb`>!$ z4ut!=C4p^*XI7^wNGpJC7M36UdNRX5sI&rQ1Xx2$dq?Kyny!=~7wo;Xqok}d!dzX$ z*n{7n9pcHwY|}Y9J%l7Bj~eg38c6h0SS~3euRv5!PFm|054K5jSRZLDB}YbSv!7(S zOGvB&+mr|6bg*=;MR;c?H&2R;JRPCXl1RMRG^xs3&e33+PA<+C@^W|wgl=Z7MHp<7 z$@a-HV4BuzY;AxDpc;p?^bhqSWT2x$k(E)H3k`@WTP-66C285F&Ta(D zG}K@!%E}Sc!7}4KaB_f}1bzFy<6xPFYIKskJc$LCY3s_w%YtPxbb>GaG!5;gMOVPd z5n#ciYp$V8!ISCAsw^7W@c65@H^AH(+6*FIp1?Fi#ztgCQ&T--GgEzSk5fO+fA(|% zvrHyYUY@|xMcSH7Wvj%@(DhOL*^LRkz?mzw!kU+=iMj-(AHllPo7i&@}bOEVI5M7_P(j&tsOUAS0=0=#yTKkTBH; zqSI!1V3`DvHjZgMD7H+zj1`Y%f+ zgtT+EUVeNbUG6{UwwQY*68+Hz*8QJ?RQ+3#NOS`xss9+r2Ow2+RRbZNDm9R>I2`v+ zIfYYL^3PfjH!eB=OSChheg8;bK8+>+=7UAGi$vuS%_7n0Khk5dWYM@dCRcnf5*=qF zda}Maif|K)d#SFptynqm?#Fb|PyN4$J{51?(#27;C`lxG{2uQAc<|uQ4^rjWT66gs z@k0C%uS0zx}vcM#%Vgk?6^-Qt_2%lD^6BG^K~<_B&dRHJ=wcLpE%*z}@G20R?>M6kF zxoWRS^!qF5rf6}}hf5)rF8WMtxqC!!Jvf1Xh#LNpz6`7e!gg7RJ1y#g>jshFAL(dW zCA6(bbO5Z|wo(yN2y0~inGQROiJ_*f9C+bi+{%x{kbUldzt7Rl&&IL z#NLOu2>udOS_2_(b&{JSe(9BB_VH_+aQq(8m)`>!`f+}LV{8<9-U zmoNYL^==}Sq3aA!o6h|F?AhtH1Qi2VdMtB@5g-X}Q8Bj*TwF&ket&abM?qD;2Cd-n zo2$9CNFq^?U(|`D+ZIlLda;W`Wb4(U6UnCNyr~!kn9&A6k!a7@t z)8n!;c)i=UmceYElbTY8w3F+G28a5b$Yi=Ua+tjP!?CuZzUi?rraX?INM#$xPoAIc zZOMq_3qdxkrFWoDz@aFq+92Th0!IXv%1|V#mTW<)N-X`#-k$EB)+TThr50yr z#H%vc>fl&#D=Kg77vSWiK!|SOSUm{bjI-3yfC6VzUV7V??)M_OPQC^6osHW~gl|MdydJxpa zgt%GjXfu^nP2FpewA;GXt2LA;6dD~Ihb&E9OXrBspFMl~bPq^Id0Ogga#$RLwSnMe zv=3XuQnc1%Y8jiF+wkpV*V*sHNflaMWfUCWUmL`*9=mHb1agdu9lVKPZ z?C9tfus)c(&d)dROVKx@dv3k^xw8%_dDt0ibJ(iJj)5`A$kWz9N8ci$wY!p^C7+p@ zBRuxx{iPD*UbStn5UKb(=yTXA>iTN};}J5lS#6IzxV7814z~(>&iwrR<*(=4ki3hT z8jH!%v38HjKs<+9B$vH+wnxC*a`)!q)A!%DAz7ZamZ~yM&(bw4Gp7#8*B_besLt=) z+;i*2yIWjH_czv{D>2MHLo!PpV6)tHWW24Ek5)$Pe*WsGn06%VX38WhF}-3ED;fY3 zRtz8S=lKC*)P4Hr)s_w<>!3+hQdAB|&g9J{e1tqKCK&Kg)m);-@H+hxyrBN<`oAa}qwoG+ouk%@h;pV4TzWii=y{|Td z2Do|l&fAyQdhG~`Yz#NgKY9E5Mua?G*&LoW9J_h{!4b<npY zuWe#M3Wl2{ydoFC&8X6}Y{1Qy`J<=KH&LWXEDSeCyQ>pG#ycxFsseD6H?-y8ky?T* zfhoq#&g$9*KfulC(wLY=3^&`xCTChm@(NS~3^!Y&-r$@>n!>b3yxS6pIaI>VNyT8Afh?7@xKzTzLZg%x;IB|TuIw>+W zskCX+bRL&qk`xmMbU`wo->cST2|H6|( zxEgRXy`i(at4awcr)Yz!K70P|@knJ`qT)(<0*OqaF&GrB(v7uAfl(EOfScKkZJq5t za(HaFm$muH;<097$nsUNIR)TUB-2=Iji_vYpQH?Iyyoy4+e#UD1+pgKX6lyj$9PH0 zB|hRNO;g`6)FmJUMr}q(v7l+aJVAkO2e_Ft*jP!gsiLQ68x&g$GHF#+6_pn9 zL?RQz&5ru@NCLx6Rc($}9v^Tsx1^#tlMP7E251;)66i{-#BfuQNjE4F=8JJNt)L() zoQfxr7@(oINuVgb3V@8v)Xr~eXlN3GZMsHgCna#n3PcL5r5&tX|1t35l>K5!Em#Dz=EiV1Kc$6 z4ypX?a|}0K*O(iskO_)P8dlIaVKq?{B_f8KYBs*1Q@2h->-w!R(qVz0M@iKLaMOs( zH6|+&Fx*tsbMP#<_Tb5WP!r>BqDi9>poz@@HywS9jX7i`G8My3s+ziyP2jmFPv1Pj zaMRyRok1ZJ$qYSL@FQCWS~E!I+B7wNLlbjL3v+98L&u%(-n?3T{c}6urlX0j3Y9`2 zD;qd~W5U}}oleya^0%{f@$~ic^>TGiJukZ2x9$3yyMUWomRf8onWV_pwTnTz&K5eF z+NKdTjRi?bi3thGmHQvQyI6u83a57iZn_x&h$t#CbgW?;re|Snfjs<6H*IXMsc1TS z``Pn%p8;-~bI3#jnXX~vo%{heledmFloU?<^uyD~Z?6I_igc(X0-m96;2ewLX7%nt zK~Y-MNXzxbH#afdbWl|S&xX2*V{|?baI*xphcWM!lUz?n6-d)w@Ajy;oz2jE;dd+Cck6-IJ6 zGC*bi-1&=N9`lx#QG&IoOx^h5(`UvEC2$lmaQb5lPHu!10B7~k+|GRsN|1s9XG~le z*olNksBahmXI}gGh+L5;2&tY8&0B1yMd3CW7_~6I@oFy48 zM{a&QTO8`VHoDdY7MwmoHuXr}FD|_-CrnOS8plQ{tu=@&sH&LV-IpKi9n6hQ5p+kp zq(ykSd0^|!z_i@F1T9HvSt4?1{qCCsb;8`@N^d1eDOnsrNz1=>R+ts%ZXcQkvJOE> zSqbYEq^0GxQ1+pRFYnYwXRaqKmzKlf2_!`lft|pM@w9c0Ne0Wc4~UKrF_xB*B_R~o zefQyPa-#S06;e{t(lYWm7AMsB9pD^XlmmKqt@Nk!Q0xCsNB!e;B+dE|hIle?>nGaDR` zL<}~|;$6^kfX!7j7R@p!6-Y>FXGa$Y8eEvdR;>Ua!;@L-lTuQ%GK&y0G_zT4W`L8G1*a!cTemJQC0<5qB?u8% z`jKh5m4cpO5Yjg_)Yg=jk(H;Sis`|W(3E5u8Eoas)U!+~tg3IH+WFKJo81k|*cnYej3=fVk&cw^h0B&mAdQ=T= z5@Z8z>T0Pmsq!*1a^PUl@(zwG$^)k}hMPnq2fyy!GtkWX8mcrUIT=|wMGQA1^K|6# zG9bXQaERS=62nbh4HiuiCo7MmXh7pQMY|FdPkwEu45GX`A?5_gBk}NmXd-3%#eyIfSZPvYHBnE1-t?QaFeJ=VyIah z_~rhK2MZu+z*bVgI*&S2u%wzZORmsauEE^3&s@ z(zkr~;L*!7fSW30d8jmz%F?$%4lv`&`FB($1_uegyLA7T7nia1CIc@gCqtwtYgzdN zZYB?u#D)gucb8s#^!&ONq#KcOGSadXwuYHIw%#o0%ZmxcaC5`G$3I}Wsi!0>B`wF% z)ms~h;bvuDa)=E8M%JD;7ctyaC&)-i$!Qx|gl1v5$rHwS0dCeF?uPZI7Ewk@QdZl> zJ+TZXP%&;&aMDszsLE9w7~ zIRSH%VWA95T~T!T6TDa~OY|o!to|*;fqzyU+Qmt@2O{wHL(q6C5^ec6Y2@%vvmY;=)uLgG=zx$@S-H#boo?A+cK`VBNE|xIDUoORb z_a(wmSrFVVl z{!u;U@sfx$#Ax08_;kwlL%J8Z!6Ez>^-&!8eH0Kqgt-JZ(l^D=hd*>LM95EJ5q|_M z$pby{A)NX!LiS)!*Qra%2+4kq#T;K6Kj6~-5Jtr!D$lW}h`+@V@&eDj=#||EpY#f` zD;kseKrnb=x4-<0Nw54Gi+l6^zEYrEWN%>)SSUox)Z<6L#csio^3Sjli^Brg;NSB3 zD1WJ+g#VOAsPTien05JEz6fplaOp|(`?u%Me+4w$Qg~VZ~^l!{R$fMq+q#Fw2+COYrg2s;t$5fC?{uwcnVOf*`ojwFu zmP}mXsUpdedrML9y9a{+v}Dh4o3xbpYBgFRPDJHPQSiGP6aQye{#T!U^$})K6UhC< z^|8f%zxy}DRrm{1=0A``2s7ba(24s2x^_v)-(@KOHEISV#IXQ*Ujls%4fdz#F4oTf zTVRUmK2R!}6f-vKkKg`i5(Rwie^m9aZ1O{xFcwZ~iz5ktCHH~e&WDvo_P5EH?e<>U-q9` zHPY%4b%JN4; zwdorBwT^cXC2#!G`%kW(*fYO)-C*yngM%nUe=jpQ$#bDXXf(y3roewEMmhy?*xo!NWU0 zT>Rwo<8_wc_2+2lo7>$%gpN;M|MB|OtM@P8JpAFiYom^2DxWQ(WoDNQ`JxX#diUt% zi#Nai`R3Wf+nc?V$aJPEN7Kap4x-k7_3QmdZ(hIu?d6LHPal`iNK`t5#n!Wmxs3<` zJ$G+>`}D=z*Y93EdHCuUmq_I^B$VOI9OTQ~e*4Dvzr23;_U)sW_Z}TL$3s446?K!f zkk)$qv(LW0e*49n$B%D)f4`4SpfVUtj)t-GB$6n|ugExnX!y?0&z!z``-dOyJo)Ly zr`Jk|Br1);WOIz1+mJ+2Qd~?2k{XY$Dlf~+C^XxRXqLU7?m7MWPv3m^^Sx^qZ+sI? zq9{@6%4{_w*A}!Y#y2s&sx=L=WW?3iSC(dFWbZ;W^Py8mkDWSq^_w5Q|Kju8SG#89TngbkuHd=6!P-YqS~5vA{uwY;q50rIeGrlwacHK`sSRi z0)?VPqte0g-HN2^r>3_Jx7Rl$uWLavB5t;zrYtjw+uE`KFW7%#=bkg4d~)&pR~HVQ z-EOW(p(;^nbQJ^JHsGhu9oRflUsqQd;@=Ej#+bU=iu{bob$!h{5KUJwH@WA)@e{|- zp8Dj}*)csOni7RZWvZImf+L}M_ryp`olw}37u1Ag$3m-Hd4(AXq0uAlJD}_4Hq0y> zIDPKuiNhzZb!d}mG#~{SY-1bPZ#3)}8EmX=Zt3ZZ^k_hGBCo_2L1|ucSY+}<^L9im zoEq7*dE5STpPV?e>&HKfbnH#G-C*18a0T~$FwVt7pY zY}+_o2gwcAaQcW-=&{8JcyqV<(d9n6Il5)OB`_ZQYp`P>_J6T|5$xX|J=^Qr!IVR`@zF|cRxF^XUlk(uqr<_CORpnq<6|?4$_h6syE{hbb{@U(?X92g{`Bp)Umo2#)!AHMnjX%L$tkI<@0r~- zQXC;fk`dYY#Wm?saGZ$dAROrF>lqvw9Ud6$-BiqCF_=nM^MiTU7hV6ZEdYRC(m?rHxw7ubo6eRKYE}!97w>J?ArRy@|2|PEU#Wf z%kJ;&>+J(u7#Zjv7_@}eqXEG}W@wtjAuVoxPfw0p1F|mYoZH>iKePADS3m7+57sp@ z#GDW$k|Nc_*3qwfVSc!^cXD>`nX_GShA1Jsy1$-ZNT#53>hAqt|Gcqhu(k%-HEi76*xk2r_vvvDg0upWq)1j$`dE~*GK`n@0q~j@kX&ym68^1p@JZFRq3S!_K+$+_A$4 zcFj%>wN)3TCPapZrWSX0#Xz-^+gn-&`-l1m2Kst>dhM7hG-x}pCH2(;WK=S=Z}H)e zcVE0-{Po?}bEQoqHT8nwgL%wVayT4xkhm=JKdM&CCex|uJB}=D-O$_CRF)MR6BZGZ zQ{7+Dh3J;qEe-8m-Gg9ZJ)LcX*0c|`qOx^d1jx8$?%QXt7N5U-_Wa)Wr)D~Zb;7#I zgT6=#Tzlf8ptHbgFaW|j3|djZCeiGs_HF8IsjSM0jR*+|O)PG&T>~{rWKTvW)&)HbU_CRIJ#7oR+PxcKJbt#b?09d*LyO?w@Yv<$e~j$6jnH*@Us~I^-MUvoB1y3Ya5?)?TR$f+CTAq(*l9XszNo*B0 z4P65Ry|RrJ`6-d>1N_$|l$VBgBf5K4DZiKRiILIcGKHRVn92&A{ zczGsG#n`41(Rd95SDt=%YF}5IKrm5-mP>q4EVXJ?7b4@q`>2h*tEri>N0Yysn|nx# zOLz~WTU781cr_KxEghZB!d5$)GLyz;vQ$~>CQeOAsj|NB!sUg@7J;C7oPd@|e9*a~ z7m>&WhK_-uv9YO{nL$>HgEPFMN-v^^H&zr@R#(@zcDA$#i`3~%Wfq-6XR)-*Ma@XD zwqbDl&atMN+Syp}PJd9hd;k#$EFFDJm#K+eyQgDFLIZ#}Ev}=evXWQP*w)fmBS@q( z8Okggou#a5Y}<;6C9MO)olUhp?Q#+lA2cl=LJ~x}jzNvFw2^LnsdZqapj#Juo0i_4 zQ(nO@<<~dxYpQ){m=!XVm9eh^+K@ut=0TyLc5V$?xdPL(eAx&bgH_cxFytFa8JG$p zBa_RzvOtSdc1LMNm9VOoCoCz0wI!3mz<`IzF>z=ERGeIIS~@z~ni}ewg~=tA4LwaT93z{G%K5_D%DO5(zc>=$pGjw^fUWD8w;_Dq z^pK#beTpn8CDyTQ94#j^G-}kOROx}$JVE1RA-qLKV@YXIgP@|Is;0Ea6NV)$A7Qj` zG|e60EgI(=YU_JiaZ=KljujFU2uEV6vH@n9CQUqE%WxaKL@p0_m`Wb6JU=%tXEg&V z!sMv1;q-)E8^Sg1sIF_6a$f<1MM_dqVk26npu|^3F!H5}TG|?V`||<=+7O*wRGFV& zR#=>upPikSYD%Ni7%UaGGMi;!53j);9j|Tao}kLe%78;xQeqQQB0wpyaMcVKbT;=4 zbW}Fv#b&2Qa)Bjq;uk2DK$BJ#6&ECD7{QQ$l{g#PR&zC|@`EKr+t>*vuqQIo(knp~ z4M&0QQ`a+c?P;y=>Fpogv~he(S8Gv4HqbY8_w20F^76{8!mPsVlniZXAZ1lmbv9E~ z+q@OwQYX9Wd!~971o*3dJt3K?wZ9~&MV+c-J3d1`ikV_!EY^H0ky zt0>OT$xYA6$cfVcoMAFK(B+!OfCZ$I#N^0q{$yj@z-;C!9DH;mE3;}6(dEf1u&c3k zj2s>4g_S`6=+u@ivs1HkM|S24V2Gt>WE2#qre>ulCZ{B5!V-YR0&`Z?G_`@_CZVZ` z$*IM{y5^SVzP=r~Qg|HjjxsAZBN|0Z*A%`Kh#!D?w|~RL#LV2n)Sg{C&+pR%r9lZ9 zN!bNSiHR{0Q3=r;CLJJ@$y8C*HM6KeR9jOpQ?wdT+zhQiQ)Aan*{QUI3BMWDCCS&g!6Ca#e&5m4TiA_?8_WL zZA0_;zOOEwn;&VY2U@;=a$lA-&?Pt=rj})9wbsoiC}X03V&mw{)~(Y!=eO_Qzx%>& zYfzh(9F>%knVK9G92p+x3*AkpvQ(7W8d?_iptk6fXRm+$a{EYIZ7pBeIasIS-h~YrC(T6BW!OUomt3{B@#fdEU4AAck~DdjV_#>o!q&5+tz)D_8d9> z=~rLu8-jCl^zgWhxTu)u7_N_>myZpcPGdlisc7h%Si8X|1V!gw-}-#(WJ7H=jJuwx znZr49BqF@8+$!iZZAUlX;HdcG?OSGd?>Vq=5Z*XivQdD?+RDh3< zN2o26!Bzp)EOi}YOK4nL@h7(~AKuVXRmtafb@pzUIhZ4_C{`>riImiJpcTU7Qp$Jj zncchd_=#hCM@JglqZ$z1Cq6YLCOXIC19m#cz&e zbt3uDq|jiW!1Z44_G_)IRroY%3QYxeNNOgQegI@XY131alL-W>SO;DjCJ45!YhXxZ zLRvQe$S0>R-QElE9+SLo9oIkD%W2IT8%qs%3pfwSfRl9Q7666{fx)Aj#z$-ER8Rud zk;QF5c#geqU}$t=TGsHV7cO4CHw;R+;i17He(U`mZ7j_#^sr`OvsG9e9RqUrMyv|}*3s3;e6@)g0~`5l zRko^@iK7clR_+l4y^Zy?3>v0|OqL%-1a)<558w4+vB_x-=PzIR>E@shOuZ4I{{GG` z9b+nSkEtQa;fE1{W$x_hw>~^BIeXi; zm#*A?+{gqyq0ydh-mb1|&CN`V%rq$sSmdZ`=opxquZ6c!aPtxtCnpsF@QEu$lpR7u zmcG5Sr$0A5E~)P8>)+jc@bqd=H^O@*1bTzL*qB=xn;L0TnS5nQmYSxv(P~HgHbmeW zre>!GC3;Xm5qLnz1i1l3CaPMmaq;r!M#iP?ymtHElZVelU$#fU*2F$8(#zA?W38Q~ zfv%1o6F?WLrwT`1)~;zoB$4U*lu$4CJeY$%C?m;B_aY@FRbwkq7Z4m7+w{XvPww7- z_WRp+Kh{8lM<%&=x~+A#HPqG6)>Ef4SWK3RDo5MY-qWTPkpr#Qt@m~}bl}2T^kbvL zvKed_&iNRc!5ex9hD2nXU%dU~?#ri(&llgHZpC&_$95H12d~NuAn$-$FrBeJi7DZ{oB_s z-~W2Ppaap@q(nG@X_y#mYw2n0s4LM_z{u2fjcnb$nh`V9%soCDPGzc62wXiX=%#|B z3oBchiYGT9HVx)bGgA{|GiwJ|uR!kL!>5lHAK!oa=GWIRUOat&q!J1UO>=d&x3DnR z*3(ntXe-m$Y>pa7!^pS-oW1UT@3f7j7B($~@8sA=#u z`P!;l`c}5K)>c-QmR1gK-oEuupS^hY^4C{S9zT2f=!xjvZ}Z*&N`YyfwsyAW#?bB@ zbsY_wvYG}54xO5rdwMk>Rcix}lo;PNh8oIXpE%t_e^@YZ)HQXDOjes)+Su5wao{_y zbaHlaa&qQ7G94V&*xRplbFH}X=;4dEuYP;8`0ByS#d|N`zPT*~KAm9^8RTGR<6sU8 zLoGEO0}4Zx!_m+&GPiT__7EaXk&b0-n4gWQh6)or15}238drm(t!r#%VGSj%b#`%e zbNBG{tnpgu;oBRIhvX1sH?&Tl&N80vBo{IsnpNj(bdDt zCm?9u`rweTu+XqDekeOQI0Vk&2F7&$BzpDsx#;F_ad3QU)!dcaukOBh|Hnc;Sha7! zn$?zIdd3Mn(3ICuS+Q?5j2c%Zb^%a zh=oZ$B{?OPpHh>A13(-3;r63Pi@&~qeWeSIQ%D^TNk}T$aHHD>0M5hD!pc-nM@tK= zoTa9&tg5DCWar@G?GqVQi)=(%YHK~rZC4u_S(saTh){}&OMHc}t7l|#e#b;_d2v~3 zW>$7qMrL|$VPS6bj<0`t^#0Y0#b56)2pt;Git>!q#&Xy$!a+t$Gecc<71(nyS!!%m z4PAXRyS1Jkp{W8S8>6o6WNB|>sb}B|C&si2eB9D%yT>=pZJnFkJUub6X=Hd{bYgt^ z$i>Uoet!M<_0t#kpFIDzH+Tx(J;1`qKvR>grlJBPM^{Bd%gEejjk}+B5-cO7GPLy_ z>@8Ni^6HR=DBdfsVPebH?em*A^tQC|8fq%5+s3wCez5rd?fVzco<4o?ES6h{(!h#K`uC@wC-*T;^tFKo?p*I{v314Gnncjrdk`n@Z78bV6ObvB4HPzIV zl{fV4J9YE*od+)-JYT%~;MJ>N&sfgDT;^tBqOYwE<^Zs&tjU2{%+lW3-8(QXy$%Qx za5-DH?%%gCHM3!$tGT9%*fx6V=9}kF9=v$+;`z(RzrTC`UF9s2^l~=R(NW=W_{wgd6@rdHN#UA(x#nF)PJ)ur$7&TaEkLv4-Cg6itV{%t=jik{rN`RMVx*Dv6h;_K%f zvuG91&qh~+t;**R02Arzrdn_c$ks8)KRCJs9E7Sqn@{fEK0mslvyoq2R$N}yKEG@4 zXw}fSzbw9deE-R_KVJ22L5xrj19cU3RTc}juym@vvA(&vt%I*ma8yYS%#<8n`24Y> zdnZPQ+W2*~G5KXhMY)MiD6{w2>qn1Xy?Xid(XR*QV9T(^Ktn@KRfPqfCmP+%+SJO` z-7_dEG`a#d%<_@Rdk^lO-?X8fu+!|yL%pEaC^A!VhZ zuBr-d6*7fNVXStvcK70jL?k4Y3t?K7(=IxDVB6g6Xm@>cSx6(2ZJzx4`HNSNe!2hd zoXtFZyKiF+Wlw8e)hhP|KwwC&i0xmEX zuDD9 z6M&V;Y8=1w$K$WJ)pK_shO50HjSNQ)v2~gfl^LEB9UGmJRxNBA?TK$e(pKFNfL(|oGS*Rs3=}2I)k;t)~KC1;Dx%T1C#OA|XBtK5l7f9UiG}N6S-da=(J3!6uqiMG~y*G5;pT zq9iM$prp3GseN>+1^7UQoryLboXkX`B6JH$x2zx~uasZg+BGmUT5<<5Obk>h*fC3Z zB{E4Pt)Z@XhARRTXFS&2rbtqm$~t*>wE8Jjx#;AAf3wl~&< zsTW-E@RE21Hn(G>ue)b-?#RQRx2N1jtGFgws&pkKz9JvoWO$vV-uW%#6Pvbw{p{8@ z@7sv!Vx++aLI9eA0w5v?t(|J*tj>b@A%8 z-$XxbjJ$)GR>o@JxPrwgl&(lpz;n1|TQ6RPlQ*|cHk;f=$~-e29W~e%Q53-eOd{g& zM4FLrLieRxSNG(GLfbHTYfRw{*$gETNI)f+Br=0-hJFUGg5M~&@<5Cs4~Gr zhHY03SDRVb+q&65Mlz93E^ceqINI5HB>$7b6``&FAaq4YOzC=s_=QCHI5_$^^MKMd z53Ve(9~o$C?`&*t5!RJNa@V>#Vw7$!uWe#>-@b*J4Pz4%8#>Y={9Ns8bIl`Q80Vj@m43SnC6&n>~8=&b_Y=?U>#&J+pJ)c$y^@hK`!4n9}V$^V5ra zUmQJhc-Q>4g9}xTG#u2BqbH_x=dQeXd;7bqm(CtPyzAKEd^Py9C9q1=5mUNdSO0kO z@b&H6*ROnj^4y^cT^T%4iNe&fyn`e<&b)ZXe?oZu`04GdS1$Fymk}fqnWknUrgZng z;oT=s7T^5#{QlkRL-sPVaGYL=s$m86Ywh{BH}5`s`ug3|$9L}E&y|yuQy>sXOkJRK zX#pL#u3dWYc=6fGr@!2J`h({xoC0|4$!tRr@Wko!*RNf^_3ZhJm-imt{N;$=3VA#n zo>x+}0Hw``PM^AT_4@s1KmYvwmD_EKtKh^i*s+>5aJt!9C1Osu<=pY3UtPa?_4?iK zzCCp@TT)scJ625QfICg1C@MnC>4p~Msox`R?N7o3}2Vzjkq*lsuf` zgz`A{FgP9D!{Z7CvAlL9l^j-Ho}ZnRn1YeIy+`*SJaYWYi&wt>XxOYRn|5N^L;QS0w2zDZcn2~+62TvY8_1WdmPanN>%y1Q7ekC5S zpr{6WN$Hxg(aDb5ii#+V)cxK+sjN6BA7o_?CE(-$-`Gpzr5dpzHO+f0_ObuJ7qw^Wr>^BS7Cju@%y8p$usPK^p$A2nFN9ntb92FAu`!3}cg_|_jf z;ZGl6d;$&+A4Njmy4m{Lva-tB<^dl3Wih$Bz{0YE^!Ui2khqOa!0IM<_4Q9oZP~te z-`;J9c+j;#N8sTPm}pqTJk+|mw79gauC8;_OspHsfo`$od08ou+~Dx^i6UTixh;Kt zL!%qF%uH?Cx~D}JI3Rdw1w6bou)1Xch4`J|77Fj=Bce4 zCkK0~%Bu@=lcR%zW0R^kdVq?Au8yAm;jxjiiSgkLRZ8G|0^*CHKvKt8-GI)Xa$fWJ z%&tRcFWvm*-kqDL_Ro#=2+IrLEJtu;a#qv$8eny!>N`7n`UVCEherCl#%&c8;0!XD zBtb<7mLv^Zs`wof+xDHg^v$jBezBC{jVxewV}RAAJCp(9 z^!D}k4h|3Y_7y8a2SWejaSVNo)h(Q^X&IheIQ03qH*S6Z!&e7(Y#M2)Ey+%f3JQox z$ti8$RuS0@>$+R@l?;Pmw@9XX98Mee@$E`sZk)aEuZti?-*Z9nybC<8* z`02*wOXqe^4GDP_IdMV0A<0>J6%C{FeYt)ZshgIbT@uG#hmpE9-5p&W-9YSh_jHbB z6G#LC4nBp#QPeR~m)U}}6Y8d?IyO$voxJqjy&tY!`lPkBsky1KvE$(Jrk3i=^ddoH z_r|@ugh9L-V0BZ0O~{FkOiFa^g}Ea1^_#+$gatc%}-JI6VTk4ue=Z}7ReOvQ79YcNm2iA~cXyxGDv30VO-!w3~^~ka2a6=TDQqtC1 zm>3li7Mq{p3*?#wzoDtSzq_kvpue-FFHRns12+G1@?eye`JkP${khhxc#UyZ6At=-_Bad0u>UC^sk~Ik&e3SY4uXU0Zz@RHnP9r@Nyw zl?WEcmtO%#@6~w%WKcD7{`s4SZx`Rb|E#AdQZU|CS}qvdHsvlSDUZj1`(I0|QHE93 z+UBGCcJJ6SHqcg6kP#cPJ}59cv#v1$Y8Bhu*wEYE)79PG)zQ&uOi+Zz10q#k3BK+# z%IV$q^!|6Zp1*qb{>_(Dc{QEI6{Q_JGw3Vf*gW*lzm-+1Or|O&&Fr4u*wau`os$?E z61XlnxuC7A3qF8NYpQE%Z|Cpq+~fd-`~K?&rb3!2;OTEU<@ z+p1ghiG0E;>}v!Cnr0nRuWFxu`tb3+r*H0GKRz>3S6W^@xN{Bqn`;#p#3x9@e2aMo zF~VP&q{2}%$e-XBWX6XD1$g>#6UwWA)5Ry3R@SsO)pvGwbhHTS%gQF4h4{O>d9F{(_50yHI$c?9dJP_R(#O^7p1F+d9Ji{ zbq$P+^h=VwJ)9hzL(=^*N;kZ;utAX5P}|;CU&@ce6Tx1feQ`v#2}bD(TEBdJ<|J}iz!`(rj8!yGBUIh zI@|h(S5@>PVnlORNl96uu%)TKk{1RqMj-MPB#4SCrajecxWw45gg~n09PH_;3 zG#xRe+cQ;OS~}^lY!ybON&p{=r>Fy;%TZO~WJYJ^RkmW3Zg6c@L1hiEw5GVKI?rDL zCOue^f*P6zc*)Z@wU<^4M#!rq#dIvDLV>0Vj#4&@=3Q7+S~XGtuaP3i&CL|>N;1o< z^RjHjOH~;xY69PIrB&fY;Dg zU%-b?#o~j3(j_`&Ca2|;l@yfcO`KXqZyL#zl>bR`-LY z6M$HXK)UKWdUQ*Mb9{eu-?F8!Vx4>j@^8 zx+*@G10da^tlZ?H1#4T{`qmZCMd~_4SwD z-II&9Mn=UZWR#?)vttty6TQg1Bg0KiQ`f{D(Ye~%hWC8-`FooOI86%8t$nL@7t3K7 zC@B(1cb1)tmtSa9!OF!$!^_rf-m+%L=52fSy!^>NXOPWG4oynV&&-OAh>c|XlgS8+ zGF6(EmNkKN=YDYW!TDp`2HWfF>l!(Whjx|7fd`F|H6m);sDzS{Rclrct=;+ZOS|{) z-umWSpYGG|pwJ2d%cUeGr^bi)1^9a@fvgI~kva?~ok6zZy@$6yd1*smBdE4_46fT& zDo+sI38XkMB0go#`t>6_Uf#Op)qT5OfAfRSK7DN!EV+~-V}ZKH#v}%Nd3w59f~2f6 zrkkdop)G-4%iq8M^T%68nlYuCx)-fk|60iyQcNISZ8N5SXl!zN#kLLWckSBy(t*Pt zAAaY+s`jdU46Vr61a@RZQjDKB(iSS>Vwl?>|cSkytcNxt7rMz zJtaVjNqY&Rt8I=>001}dm3>w84(j4z;d^7vb92PRWd)-HT8|`+bFc|n(~IGn)#L0RrT#%%hzt*-l@$| zn*owMMAsdlJ^;R&J%_gK{eU34>a5W4=*Y;3=wPOkgQXQ*(^VlhacGj-#`f(L?X+~T zgla5S-Q6qK4z1g~$pkC~RVo0`HTMkzqMKjb{@Rzh)+&%Y*=)7R78-AgOjP1 zjsoN_Fd-FH4Fg*My6l3YtkU|X_TiCr>o<(hiNXr{76RyM+Wy)yJnxQ#?bAxi$t{GXCsY0|yYQU8VoNK{~ zh)v4KE2`c9{##$1Ik1ePn3l}+4e|~3aD$${Nq7HiVY89-u#V+My;3@*^& zA;M?{fpZPrLwML=!Qcm<9RKF>$TEs*OlW{#s8@)ey@ja}eHH{G6^`0WRdtqyd!~N~0Qby-ZQp(I)tMWe1kH^J zcXDubbh0xsGSD;9Mx+bKHwgV>Xk<^&T;HI^x!GBBc`!Fo%E9fxb2v1cxTA2?IhS1{OZmFzcM6oXCto=!to+r9vfW zuD-LocR*-#VrurzlV>j8xO$8GRbLoEbJ<}|P7GIul__9kUCaqJC~=^%GPHCcXf7ir zCqBr(#2orh9x+uaL3360tR3A*!xEEQPyKS^+{N2}K6-Sz1&ew@l8uXljidD}9heem zqNlK+qo)ilya<{b<{A|p6rqza&Th?ghnOoymj}&wO{Wdw!_`ue*dfzp8f8bp|%cobR&IjEslYOugYk4-r`%15H!k%*q#Nu1XXmI3q6H4M8Jl zipF_%S~?tU1&ju|nWe3z8RG94x0#rj0SR?-A)v9n9eIy{mYcUnZ{K}#|N8Y?x31pg z{`SYNKmd`UnJ#9Q7IcV5ps$P77@Av}hJg{?nYjoa(pG^vDWM+L#=4j)iWb@7kQIO{ zTi7t{9h_ZV-90_My_p;zI@6np#lhXp4eD5L?|DC6yL$Jxdw)E+6sx8`{RXF)+=vu(1Pl z?BVI<%VPQY133=l1W5Y%u{gfEOmA=3fWfnmuROeZ`}Fw-*Du|Ac>m6=dl!FyIfy{4 z?zW~D4z>oGfNQn&uy*rIWi4!M{X)7ZHV)Qt*|ENM7IY(B;2Q|y;hMQ1%$(uk20+)> zKM=riXjnvKM0iAaSSTk%FE}vJkD0ym_UQH7H%H%UY2R_`=5N289liVD_T6S4o*U?9 zV&iCMV4z3LwYA9#0YgC(D+gvE@LYGZ@bq{lFh64hm}A4h$kEe3v$ZPBgUN!@IwCSE z8qjfkTzoty&M<~R$6+Cf-DkM>9^K)d8JU-yl~cXx>ofN;0-x>$qty-YvcNY6DygZa zp@Co-DjG1ZB;aHS56?BX%Zv?SIxsBhut5VhYUA#om{HKQqA4*rEGjlWF^LU1iTsHfGeRB2pCx1TTo?Grk@Z7x8;-S~RyYlRNVnNArw>JV%X-wA!l%t`c1XmY5 zy16C8fyoA*>rD5H4EJ)fBxBXgkzvYkjLI&ZyP$c+>bAVp#MHD5Vhfhb$);sxW@Z4h zT7K&6wQIM3fBfL&QXZlkmzhy9bb1hot{l^gZemCfG7XNpq9z6*L;*`{hNDwV8bNf; zjJyLJ-0W>^937o}xs*HxD{Wp==c0j;joXG7&##zQSx{J1Fgri5sI07rv*W7^*PbAz zz>_O`JFJ1|R+Y@|tR;x9kFT+j-YkS#Q3ZgaNrTORp0SmUqkSNxJ!%f!)W^xw)ydL2 zh==Hg`sLPluNdCAWz)#W+My*QE0!%?xoUXB8^^ymdG7wT`!`1~-55Q!EEnON+|%MJKNOK%fr!u#Y1${1JjyUZrHMI)9U5D3)^d(>*m+A zuh{a%mD`UWJs!Pv^Tz1)-#)S?h^{L`AMPUR5Z*~_A02fKJrh`%xHyK*C5UdUr?U$W z(aj3)d1=R%b*uY3TU+buDyy0nz53SA4}QHedil=nb64)&2R;l$*UQb=U=}bLRcyCt zsz4HT3}H3n;Np>1FplUtx4-)8ORLu{U)#1AZ>laU7 zyAC6ZA8uT~PY~Ui3j-~6wP2o7*tG{3EMkdIVkr6(S%Sn^+QIhb`qn}M=&pF}(yeR1J^b_T{f_|XN;{jA#YU-3 z5mLR13B$s{!P74!A-Sr9;JF5K-`%@y)B3^grq+1`&+S_O)t%A%*DhRo^s(b^3bhEf zPU_GUE21bzS;58&3em8n*p$4cmRxA?4d%V``iAYxI$9f=(tsP}FWvUtrEBM|UVHeF zDZz7HEOel4rM5vIs;n9i?dTnunv_;p(Avera~qEB-?6fd)6_YK;JNjE8_(Xk^ZVt8 z1kaUXqh3{x!Wdwnq8XXwAA$&QC8Z_(9R$zSY(2DlJJena8z9-pb*}pK-lH4W?tZai z4`pVMHC=;B;WF*2cO_05Tb!tA73S#)(%t zcz7VW4;+3F)qbGw2nKBR)n z{FLP4>Xw$Sp5ftU!+zkoEu~*mW;mH>DRQXl@>INmGS#Xozp%2lwWF(VWOFYM&ox85 zBdR==itni;XIMLTHgp`VfNZx66GYa^P+ftF*e~QYZe2cw{b0yQ15nRVFG}1z8!bn4VS3`Ugg~zIo}~D|rOZ zHP_Wph5}K6hD=l$gUo^L+t;k!@Y3fud4R1{kQrjVv?;0qsZo@dQFflQ?VVjP&irx= z&y`+iX{d?u-=>5*5G+L1cmCnyUwm`&G56<a1f9d95#Q#-11#~0rq z{=?b#dw6)R1zk@^od$G(M4XY6nkl1b8kE-e#n0dEn-k5$bG^*W!5LNY9nnGfigNNw zG!v(&gkXO!)+LIxuZ^(<@boryD&!9xBU4yQ+xq(4K!~c$0AH4;=L_Mv4z8XqZf-VK zc3w>4qHD)0&8z6-)YaCM7UpH=M!Gnd*}DMGot0Svb==6BmHl0v-L1{#Q8>=P#LN=! zp=DpvwQl{kO(QFp^mQ*D9Bz(t(5Atj%o%uYM(57=kM3E&V#(6Q{VPY>*;denLSS%3 z_t-S=djHFhUg2y|f@*%<$~*^UIRzlvMj?PnU4{;RbK<>&2li~=v|)JD`m9+9w4|)6 zY2=J&>TMtV^w~$>y!YPW{kyj87|Au1#?Vnw(`NueZFuX~Q=fc#?3+(Nd-uS;*BUKn z%3<87>X=8N9DBnT-@o_iC!c?F;`5K*Kh)|ZBZD4QR@EcrN_T(%&XMDveSPBir$;{i zBvWQ4aZ6IvFp7nXu3gPL2VeX6*zx0E9{b?EFOPfAkmo4OP*m11M>%%?p@aM0{p|BE zzWngxcRt)?BFS@SQr0Fkt5$E@`r7M*dm(jYG-M|`SObZh|UX6BT|5f|Os zmo{zObLhZ$Wc6xb5Jc zy>Gw0_vJVDct~;NW+JBZ6T&`kU96E4Qd1-3`2BwqHUjJ2J$ltQKG{YvD-$x-A>FzPxYu z_6>VCno5$25Tr>HDmA(J9j#pzxvA;F-UQ5Lg{LORg#~%cX$F{UT(@p;#ip%Wc5Q!o z$H?ZzMl(5b9C=Aa4U2w?RQawI^>a9>k_le+V6EIh|J0O(s8Aoby2iZ}nqFyF`|_3R z*RR{WaqGs-)tWN$vK;vtsL&E%Zq3U1>fg{&0FGf zZAJZ}#RJRXlDKT$961HJukhU1v>dSjbqvC$Ff}i4PDgLBGuA#t*G!0w2=rwIL@k~J zAUB}AtfsZ2XX%o~JA%78(mG*whmT1IYp!@>>io`%fyvaF~GpCI3q zwD{;SKd*owb}thstZC+)%IbxSdb)cTcP$uZ$bh$ify&FNk_FtquYjGNT~aYPeE9Xj zku|F~Ea`2k%}mWmijN3l`i6w(F8AC`p#|sVR5!JDwRd)RE@;UGf=C5`GDBWL6Z>v@ zU|Vf!W>IV3>a7PpJaPK;k0)Q**xy`{mzEe6!txD_h$&lU1t2#Ro*MHOG&i;^Y^y75 zvBucu^*ohk08ylNd0N(-wiWC5eel_-FTXne{%dO%RpzC#UofL!am z;*`{RwKecDYN{$uK`1byT9ubq)+Io0-qP%%1)alN-#UKc)R$i$*}Sr&u_!wwEyPPdCt0W7ywI!M@M*=4pV5Xs+P?MxHFM`Snt_r$M-FdAqXWpzS&?1N>05i? zqi?_a;l$DR_pR!zot=}J80P8b8xfnBmfteeo#z4|H#9jZAuYt0IYiNAq~_%o=a)A( z*OgTiG_!Hm4HYg%@;G3C+ks;TmQd8#jcfbnwD&B1<>=?9zxwk1gPhjJMoxpu!Y!}X zFDQ(SOv){+Y+JXv#tYuL5vgz(i4O`454VTQT5w)UaY<=?9jCseq^M3G+#l*`IazrX z=woNHGdgh3&ZGUrpQ4!j56KMBdC2{-@MGiy5_-cyX*b6 zDADZv`DKaWfdPIYiHR)WxYo(hspSjGiYpsxi&9G?CF|F38ftB5E>DXO3-a^v4v37eX)edc z%{4QPUEI)6R9;zGo|6%Q@ix|F3R(olo!@o%*8K|)uHS!rthXq-WqCC_HMezOz*~;T zcE@WQe=l{7Hcf4ss$2cm^=pQfG}o49CB=jU_%OYLV`f*yETgFVCB-Mtn^RR*KBpiz zC*PEcm~nEl961RkBfz-X-7ByC`sJycqt_qZJF+sRv^pt`J!f^M`V3hf8z2G~c{6@p z?eKe0N$OwK*IreSoe>|-VtF%J5eb!Zu|IV8iHb$RVcZD}e1UrBCQ?MIo(3lNBLFi{o16FW53l&xF(Y6~(G5+nSW-k#op zF?n+d-0l|)@1Qv73v-GxLef(J3-G2ZRap(%?ee!Hu$VRlMddh-rf z3UDOBiXb6D=QaMd+BD(9txHdD%1n$2^=EpydisQ=6_)^UBMP|mw79&Y+`QDVBvY^z ziUL^!NCaEGx~Ab9k1t=nb9UcAU2$e=QpGNuWHxiWB96R;9BP!2<_&V#gp3Uf?3fJ= z^ir$hqk?@r++5rP;uC_Q)pHJI2F0Y{`v5;m3UwZP9WYWQO#*)}uIHQzIe?L!WSj0soG6A=``+NH*$7hw7 zrX(lDB^b(6%}lWH~b>X3T(NuDl`*t|(|J-Ne{A7uMMhULp38*q2*}hj;`A`NqX2W6H4UU{hFC zup}sIW4+WYo?mg`?cUm~l;oUdDh0SJuc?wl6dF~PkTWtiF)@eWu~&a0_G*v-sjHYbA8CLsl4Gl1dFP*m50*QUHN z-Po$g(Z8hr_cnzx{`EH5Luc%F=;WE&bK zDFFyqfu?8BZYVn|DAC-FnZYrH)>X%6wwo_2m>C!u6%_1GCsZiZHU$YaK%Ug}rDZ88 z>`q6@3`xm$IONWtY8V)_8Oj>aGktvmV=D?UQ0#p(eEfpK0)hj>nJhmo%o^|>h|`K% zkV>R$mgXk2XV-FwH!eWw4HOk+O&B}FB~;Tp-iMu-R|D);i4hROjEoM4fuz5mkBce? zok9>h6mHP%Itf9s3F$RJO;tSOyjVdIVg6`Hkhd*( z7bXVYMM6aj8gceeMRGb&oZJ`uwe~bxo+qg14IjyjQ3fLAjB!S#2@U)(q>Lyu9NtqQ;6RW|*6N5S+ z$Op{_$@ zJUp15OgDEI7YBPSBBg;1ai|ii%IFK{{swkdL8BVMpg~3g$Ssw}-nDdmQ*tx&%S+fr zF@8x2k&vrsOh0!IW`G~rsCd{pX^^pj7s8LS8b~?zmZs*;->Ly42GEis&bTC`gN8P+ zqXNWiC@LzeuC8jITbWvz9^Xl!xp_MwC@IUu)7jJ2-U)$HF|ickT1-{dg{aF-9wzT(5-{Bqm!qXy@RWhy*(_lu%#p0J~UGkZnaw6pu~`npxCrjsQQWuJJO}dk^^^U zxOOV(>H}hS2!g-v+|qf~)yBY8Qd!~GKy=LBuSH6*jw7!yV%=Vm|L1#(>Y))q`8=ox}>#f zdoGRsaIkxBPI3wx5O?)rwlrj01cW6>GrXXjJ(w{yu%D=DTHN2ebk$(j#;yBy$^gLj zaIu32J;TPz1R60TszL`<3X7Gl4QQtCzI*jRZ%a{Xa&l^R?%c+9whT7wR31%j`1Aqk ziHD1NUw>D}@>K(CHtv4&&CN^AfCn>d9Bp94VQmUaX(Jt?g229ynCs561{j+9>g{j# zb=0LLC#9s%E@*6C0n=JSSB?O;y0#9k-hM%e9Sd8QE?wEPdedv0cWmpOml=(Lg=R7= zEo?1}VU}U8L-ZQF4nX8^I>_ei{r2tE^D+|?*zAJY2um`MAWO{R6sR%;xYe_B^6&`= zW%mv=4=moWWz)v?zS>2B0B!*&I9OU*(~XSu^^No}P|>H@NWwV8jG)0It5Ge=i&rtnlwOUKgHdxqvMWWhF4*V)F#+S1AlJ{yRDpi9&j z_)_>#IQ9*|ZBuqkYI;&)d{TT$PF`hWS9ckWqcj5~K~Y)V(#4$_5Ehj(xVd}fE4yG` ztjj`AT3VQ!o1lGwyAXc^iz)t5Qo|9I+Cf2q@uBRLlRZ6 za32j#8aCXppoT_-reTVe#UZ1rB)8awm@|Ncf(!v{XE7Y$F%=jZ)%(sHpM3WD!etaG zH*0efI>$g_mUf%^47>wa=S;|ZYI;_PS>ebm%Ev@kbwj}cR)18u+o8l0`&L_atM+PSs0Q2b&UyN8)h4a&?gx#JgZ~qo@LikRABbY z0JA`!^c{Wsz3;9x;XprUYs@$V7vZQYtEnqP^gxVaY!uWr484HY+B&AiV!XiWiT6s1 zGQew`TES|lLL?(EbR&6tEZ(7ZwNINh;EQSAegJE>zMKx&``C9EPo2K{+8bJXE5ll3?As%qf$yZZ0DcJktn zaOu5v<>5Q+Xd=VOkYJ=5Fo#j0sgQ4o?gzT1V`9IULiI3p@o}{^*HiO^_(GH8QB^@q zXW1nbNhqZ_z+d$cY=PnEI(Yf!Pv^eB`SAYe&4<4o%NwAmIJ)3q6g{9byj_iwtb7|x z^l@N>ffe9dSGv7F%fZw@Q<>^z2wVtIA^BFCn!p$20oP(e!BZZ7ObDZ4VasSbd+Geu z3#TvN`)%~bjZ42Bt{kAK+B+KP>B7YvCUHtistWQDCzNI?0|qh!T&wJ9>=fYTYN4kE zEvQboiE0~7P8}#AL|05Zj#j%49T9Vk>2NM1UTy{^7G|?AUIEhn+ntN&uUtNNiTmJ> zoec0GM<)XW*MVD(3hd3%HXKa>y$672Bl|@Z1ln+b+qjjXjvDUD>xFm_F9~hnwUD;W zEG#U6H@92Utt>6gO-;Zv;CyYY&;IJ%uQ%`CescHPtuwc;{(SrX-Lthy{S;X@dlTZ* zuMUI(CM7gEp5j{tk_*lN>Y_w0)HC(-cCj#qx0Ncip){>fZ#bsu1AsQSfI%t4&dwg} z95R9(!^Rqruem8;Ry>YQ7`<@j%DvmyuiXCm@m6TZbS>xz52vGrvmTW3 z;SqwHc$BCi+M2mz7X|THyn;OK2q+8#A);M14@h^@gWtFnDsph-I8$6Wt{fL*9@35a zEX@r4S6sPq@!F-^Zxk2wAG`eU#L1gCN3V`n4NzuTTN=Q2P8&8gWb8wN!N{TELT zx!*m#b^rRap9b^&!$Om~-#>BZ)Xj%~zA_sF+L{j74xaN;_^1tX1Kdrn_?M6 z1S}IocQJ7fj*3l5uc*rp5AcOJ9UKxI5)vE~6yWF2@?wQFetzQodEixdzw0pse#!L; z4oGPHzE=%<5o=@23j&!D&IlF=<5*ZL>lqp`eA_9;TwN_|2NMgrp`od%i35Vw7%}`| z0hK?euC=3fPD&y>i4&m_78)895fc?!IB?`&=N{d;dG*o7-3_pdkSa=vsxJcjgkw2W zDawj%3M2+SH~^qp68Oi`)36<9`XCC6nUy)-#)+V`@lJLj$+IhKIUQ}awF|0BVVhdr zxUi*b(_8O;`oo>`cP`)f<E0_dcfy^^9N$p&BETFsQ)FU`%&OiqrAPcK}y^_$V} z&t5-!{n`&_Zry(PfiaL;8zbJNBH{`pHjGgkj92s#-^;}ZNG(JWW7DE#%X$|yRU#H! zVp3vacICFO?_Ixe_U6SK*KeKw{o%t?)jKJ=wgy-(v8^GD89YKo1-_$*jA3r>?gyyW zx?%(;!z?Jx$;eDeO3El`KX!?G?x%0hU3f5h^Z3OJckc86?OkZCt)kqns7eGUWevD- zX#){~XdK`O^{6A_Fm|_>Rpe$Q!{jn4xqRieSNgJQj{bb@*2P~gUisth;+KK^nW*9O zVT%YX4t{CGH&bVpv9Xz@e*jVRa+zHl)(y@ppOeE*&GOGJnLRr>)R>advT5|!UvJ;J zb@|ud-rPl*>1sgyYuXerf8?ne+SolIaDtVERX`vCYC~5H^|x2ggI`8o39FkzZCLWf ziC=C!xp(3Evk!r^+Uk<2r_d&^EGMU6pr@s0gy<<24A&TMz_i9;+Xs8<8p?Ch+4=F% zk{2&p{mYf}4~Uumhg!QSGA3H67>YBFoHB{(uZL5BfL7Zw0uxCTOZCLY5nPO)ME!4405tUnuC+Q?{*L zxTrEGGbJYokU)ON;-kNuL)gW;AJSf-$gxay@VZ2e#*xF>DCV{#6oHMMi&I1az)q@q z-s>ZM)!CV8`6;ddYUk8rTD*Q>QF&=jW?ldm>gM)0Zr#6l?#5?>aJ+Rk)KDV&GW?5XE82KlSXtP(c>0IW zDGNhhy^<{*3kx$-Q$h$(yP)~xlS|+3T)^5(kqS0algG9cHA1(n;Ou5;ZDsEm6d7Ax zjj$PV&J}CdVV{*3We=RTvSw)C%L^^{Q{=d_)QBz`)!`lGWi7)QF!t~X2+N*V$0j&! zaU-XuI3XH1ZF!l`n>fHi9XNw5s-d7$6wJbHZC$*B!xLfHTL{P7%-U2q-dbua6LV`> z8S)0QhGDLbUcS-nwAoEP)!yeRQVz3JNjueKpo5l^)`;=7a}A1$%PFjATbO%+B5Q=# zmqB^bAZZofHc*#_!38lnxPm1U$fLu$G>l`XB^dw%?Wn+u)> z8PF6lPGr>}z{^QX$(g72u7x*M`@Wl})-edmJ4*{g5Ce&Z7RXA?pgLu*-f`g2n-_ln zZYdKl#x>JdRcu4QsFDr0q_S_`s!!hd7ngf#XOSMfZrKnDa2N#XZla!)q)0+5lTh>S zsYAn|4({hERJN_YK9+JSw3pB?D#}r%BxlO18<1!sw(gf;?`EW9pbdrsDW0PutExdl zxf_YDjRfl4jF z(_D)3pNx%e9+bKE!;gQSJSE`a_iHWwL%paMz2S5I*&5@4>Tm5!)%}lDZ&Psi!g!sN zKmV#T`Hxht5dX%+NfwipbAOy8bSlTcttBQ4PYKjIkvv;T`?u97NSNNoKOJJCYW&{s z*AdM0Z%gYexhhaAKlzdKzpX|=!ZOAD!&UfIBXOsG?5GyOtpAR*+k1kw^3z|w$l*!< zygl+wymL|b)lMXaRsK2c7ocGN?nH$`zmMksXV`7pwBwF@E%pVP{?D^WwM=`=3-|QI z5iifF!gj9jKjBR(`~!o8`ks4=E?Tc*P>aCHM-xwFU*kSKd!WE>=ERM^_p8M}n?6wi8E- zK`rq2|Fp*rsG6vi|NE=xI=^)M(N!DaN@U)eD8oPDM>xY@UTGblC@uUu>L*&Pb0Whb zA&wwXF>xdpccEyJ=U>x4nxHED`!st>**1QTX2E9~6UXjwxs8JNUno73e^L>j_*kNQ zYVl@%mMa2ch{OQ@^|>um3q9w~LOxC5#Ou;i3pPwr=r2|P0z~DC>2<-e`ifs zO#D5iPbUcqrB+Q_#^T;&rayl^KPu3iu?M4$)0Pu^vul!`Q0n~jvX{MROZ=ix0w#z*%uF&|gb0W1)UjF%t`{ca9 zS!|5Dqbp_j^@%3<=fq7C;H7?NisqcS?h(m3_445mpK0sl9U32Bj2(rAnM=fX{`n1APd3a|IH7mkzvJy)<&f%GuHh;ZWJ`06ra)yZYP0$UD0 zF^jZtX)gEA6;vFi1&!DE-&GHRN+*-n(`>MKCAN*{iN(pGkAG>MSnIOH<)5f$^7qY_ z@pDASZoNP4xo6^D|IhK}8jkaoyKf{zs*$LEG)ez&rXEi6%E{L^=N)l44W&272ERl% z|Jl={MaJ`|7Z3S){vz?TcDfNbAxlgtIDYJ*uK=&(aJkt-QGz{+~7Yw zehlZ@7ozoH$pKCGPu(D=6mvqx8vdE|7XpOm;3`-Y9I*PSeyUA1*PTJY#W|&88_!;aH)k-P4FM=!Qv2 zN`!dtWFM*VrCO0ZT<#&d=tjeO)09l)#%BKOOwvVahPZW~>nV`W^5nEMxknSGd#S?j zrsRIMQ_PW(VtLmxZl5LW_ zuP9;vdy@}I>O(P|KVHs8IQO&pqV#yzZodm><6k{C63rhgETa7$B$`3^TC!Sroj4)1 zQJcsc%db!*@u}!^k$GJx$J5LS5@LT}5-RX4Zn})(#9rldz&_(^;sL1@`F3N@LAG^B$!9bMGcnP2BGLU;u&4p=7cnHayZ_f~Z zEar+XhWxX4#+QviF&Vgj2#6}m=IqXGmxYGk(DO$>X_C1(?sA1b*5V*>^*Fb9~Vc5CbyX^PnXAA5h!WC@|vKd%4U_~#W{{}Wz@PN7;SFGl}j zH@358V7|a*5r2Y!%9^WF3D< zpmAL8r!Tbr3l$liPWd8M{QgF?hgm{v$OZ9<^h#qFO+r}&F8&zj5d1Ql z7X_Yjxt~3s1(ymH;EqgAJ3diLq;MXAtB}b|yp+Uxfrm&6Y)?gUj$cww3m2G{XW2gx z&NO+oT$E~p@Je`TT6uo5kdg?F_twDGCV;Ux7R#M703=34X?`ef7cyUXuQ*l30JApXY0 zBmVD`)zc}+#b{u-PuESOEt$h7CYtc$_a-k9bV)P=xjw$wKsX!4degKoPcr{mA(9uD zRziY3$u;q}Rw%Q}nTh)%zoP@jlez_2%Z*}dN4fjcFaeQG(j@gu>H-b0oJRjLk@d-) zHLJ${wjUGAPsTD&2wnn|VO?4*UzU?tW38Hr9+*aYUX*@G?mD@-y67=PE(5e!M$X8O86cYS&ao6J)XSE+r#ne_^X7}`1?&Ia${T$WW2pBT%a?dxlNg}xe_UT z*Y2;X6nRq}ZLn zO{?ikP&NwI_vhosPk$S|a8giBC%k})p)zq>SL_@(K4r<^m66;hQ2$7blbIE^j$yvSlgnKiK}4~lNAm5%Tl@O_OC z!+w6{BnMS2SGyh~OhI}7*o6IV>MXN3!##f_`+sKzE72fsVS6Mr%qSFTw~DvUb03KI zK53;-@&v*l+~ya$$`cY8C%z^5?V7^SGy@}Fi00qQYYdBH zg=eB)n8-iMm^H~o1>YYc@xcFXSpD7B=#Ts%TrvOZ$7u~B|1SOK=MNPt% z9MW0h_Plrc$ECvmgIfNIgmTeW{B9Rc-01wzv_LxHZ87#VH%5dE9RKPy{?RiDw?6)Y zD%=Opj+VZ-g#S<4Anm{M+v#V+tvAxl{t4w5E-USsyZy6i?ZEC28ND!-f0)rKB!Ba} zpPoE_ Date: Sun, 10 Nov 2013 20:39:35 +0400 Subject: [PATCH 225/434] OpenCS: set current directory to bundle location on OS X, like in OpenMW binary --- apps/opencs/main.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/apps/opencs/main.cpp b/apps/opencs/main.cpp index e5e7514ce0..76869d0afa 100644 --- a/apps/opencs/main.cpp +++ b/apps/opencs/main.cpp @@ -7,6 +7,11 @@ #include #include +// for Ogre::macBundlePath +#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE +#include +#endif + class Application : public QApplication { private: @@ -32,6 +37,12 @@ class Application : public QApplication int main(int argc, char *argv[]) { +#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE + // set current dir to bundle path + boost::filesystem::path bundlePath = boost::filesystem::path(Ogre::macBundlePath()).parent_path(); + boost::filesystem::current_path(bundlePath); +#endif + Q_INIT_RESOURCE (resources); Application mApplication (argc, argv); From dfa900e4e3928f3a997202abc1b11da2ddad6b97 Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Sun, 10 Nov 2013 20:58:57 +0400 Subject: [PATCH 226/434] =?UTF-8?q?OS=20X:=20Fixed=20=E2=80=9Cmacro=20rede?= =?UTF-8?q?fined=E2=80=9D=20warning?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/launcher/graphicspage.cpp | 6 ++++-- apps/launcher/main.cpp | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index 52fa192d55..516c3d8233 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -4,10 +4,12 @@ #include #include -#ifdef __APPLE__ +#ifdef MAC_OS_X_VERSION_MIN_REQUIRED +#undef MAC_OS_X_VERSION_MIN_REQUIRED // We need to do this because of Qt: https://bugreports.qt-project.org/browse/QTBUG-22154 #define MAC_OS_X_VERSION_MIN_REQUIRED __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ -#endif +#endif // MAC_OS_X_VERSION_MIN_REQUIRED + #include #include diff --git a/apps/launcher/main.cpp b/apps/launcher/main.cpp index 0b5e62a66f..9f89f28102 100644 --- a/apps/launcher/main.cpp +++ b/apps/launcher/main.cpp @@ -3,10 +3,12 @@ #include #include -#ifdef __APPLE__ +#ifdef MAC_OS_X_VERSION_MIN_REQUIRED +#undef MAC_OS_X_VERSION_MIN_REQUIRED // We need to do this because of Qt: https://bugreports.qt-project.org/browse/QTBUG-22154 #define MAC_OS_X_VERSION_MIN_REQUIRED __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ -#endif +#endif // MAC_OS_X_VERSION_MIN_REQUIRED + #include #include "maindialog.hpp" From fa138183607c2b02787abd9c9b955dcca77f50b9 Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Sun, 10 Nov 2013 22:04:13 +0400 Subject: [PATCH 227/434] OS X: OpenCS packaging --- CMakeLists.txt | 8 ++++++-- apps/opencs/CMakeLists.txt | 3 +++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 578c6cfd63..01f02ddb96 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -685,7 +685,10 @@ if (APPLE) set(CPACK_PACKAGE_VERSION_MINOR ${OPENMW_VERSION_MINO}) set(CPACK_PACKAGE_VERSION_PATCH ${OPENMW_VERSION_RELEASE}) - set(APPS "\${CMAKE_INSTALL_PREFIX}/${INSTALL_SUBDIR}/${APP_BUNDLE_NAME}") + set(OPENMW_APP "\${CMAKE_INSTALL_PREFIX}/${INSTALL_SUBDIR}/${APP_BUNDLE_NAME}") + + set(OPENCS_APP "\${CMAKE_INSTALL_PREFIX}/${INSTALL_SUBDIR}/OpenCS.app") + set(PLUGINS "") set(ABSOLUTE_PLUGINS "") @@ -746,7 +749,8 @@ if (APPLE) cmake_policy(SET CMP0009 OLD) set(BU_CHMOD_BUNDLE_ITEMS ON) include(BundleUtilities) - fixup_bundle(\"${APPS}\" \"${PLUGINS}\" \"${DIRS}\") + fixup_bundle(\"${OPENMW_APP}\" \"${PLUGINS}\" \"${DIRS}\") + fixup_bundle(\"${OPENCS_APP}\" \"\" \"${DIRS}\") " COMPONENT Runtime) include(CPack) endif (APPLE) diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index bd8a852b92..f3b93ab1b0 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -188,3 +188,6 @@ if(DPKG_PROGRAM) INSTALL(TARGETS opencs RUNTIME DESTINATION games COMPONENT opencs) endif() +if(APPLE) + INSTALL(TARGETS opencs BUNDLE DESTINATION OpenMW COMPONENT BUNDLE) +endif() \ No newline at end of file From 96714fc2c136da990d9da1b4d6416f5a23e0e98a Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Sun, 10 Nov 2013 22:19:32 +0400 Subject: [PATCH 228/434] OpenCS: proper working dir & library path on OS X --- apps/opencs/main.cpp | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/apps/opencs/main.cpp b/apps/opencs/main.cpp index 76869d0afa..1a3f63c4bb 100644 --- a/apps/opencs/main.cpp +++ b/apps/opencs/main.cpp @@ -8,8 +8,8 @@ #include // for Ogre::macBundlePath -#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE -#include +#ifdef Q_OS_MAC +#include #endif class Application : public QApplication @@ -37,15 +37,28 @@ class Application : public QApplication int main(int argc, char *argv[]) { -#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE - // set current dir to bundle path - boost::filesystem::path bundlePath = boost::filesystem::path(Ogre::macBundlePath()).parent_path(); - boost::filesystem::current_path(bundlePath); -#endif - Q_INIT_RESOURCE (resources); Application mApplication (argc, argv); +#ifdef Q_OS_MAC + QDir dir(QCoreApplication::applicationDirPath()); + if (dir.dirName() == "MacOS") { + dir.cdUp(); + dir.cdUp(); + dir.cdUp(); + } + QDir::setCurrent(dir.absolutePath()); + + // force Qt to load only LOCAL plugins, don't touch system Qt installation + QDir pluginsPath(QCoreApplication::applicationDirPath()); + pluginsPath.cdUp(); + pluginsPath.cd("Plugins"); + + QStringList libraryPaths; + libraryPaths << pluginsPath.path() << QCoreApplication::applicationDirPath(); + mApplication.setLibraryPaths(libraryPaths); +#endif + mApplication.setWindowIcon (QIcon (":./opencs.png")); CS::Editor editor; From dafe80874ab096f018f2642e809bf620915c769c Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Sun, 10 Nov 2013 22:24:31 +0400 Subject: [PATCH 229/434] Added empty line --- apps/opencs/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index f3b93ab1b0..f87650b331 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -190,4 +190,4 @@ endif() if(APPLE) INSTALL(TARGETS opencs BUNDLE DESTINATION OpenMW COMPONENT BUNDLE) -endif() \ No newline at end of file +endif() From 05e19b37e3f0c0836e18c60010ab9dc32b0d27db Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Sun, 10 Nov 2013 22:25:29 +0400 Subject: [PATCH 230/434] Removed obsolete comment --- apps/opencs/main.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/opencs/main.cpp b/apps/opencs/main.cpp index 1a3f63c4bb..344a9360f6 100644 --- a/apps/opencs/main.cpp +++ b/apps/opencs/main.cpp @@ -7,7 +7,6 @@ #include #include -// for Ogre::macBundlePath #ifdef Q_OS_MAC #include #endif From ef8360f346dd7cb48358709a221616551357256e Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sun, 10 Nov 2013 21:45:27 +0100 Subject: [PATCH 231/434] silenced a warning --- apps/openmw/mwmechanics/alchemy.cpp | 134 ++++++++++++++-------------- 1 file changed, 67 insertions(+), 67 deletions(-) diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index ab35dbdb1f..82580ce0e1 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -30,13 +30,13 @@ std::set MWMechanics::Alchemy::listEffects() const { std::map effects; - + for (TIngredientsIterator iter (mIngredients.begin()); iter!=mIngredients.end(); ++iter) { if (!iter->isEmpty()) { const MWWorld::LiveCellRef *ingredient = iter->get(); - + for (int i=0; i<4; ++i) if (ingredient->mBase->mData.mEffectID[i]!=-1) { @@ -48,13 +48,13 @@ std::set MWMechanics::Alchemy::listEffects() const } } } - + std::set effects2; - + for (std::map::const_iterator iter (effects.begin()); iter!=effects.end(); ++iter) if (iter->second>1) effects2.insert (iter->first); - + return effects2; } @@ -67,7 +67,7 @@ void MWMechanics::Alchemy::applyTools (int flags, float& value) const int tool = negative ? ESM::Apparatus::Retort : ESM::Apparatus::Albemic; int setup = 0; - + if (!mTools[tool].isEmpty() && !mTools[ESM::Apparatus::Calcinator].isEmpty()) setup = 1; else if (!mTools[tool].isEmpty()) @@ -82,23 +82,23 @@ void MWMechanics::Alchemy::applyTools (int flags, float& value) const mTools[ESM::Apparatus::Calcinator].get()->mBase->mData.mQuality : 0; float quality = 1; - + switch (setup) { case 1: - + quality = negative ? 2 * toolQuality + 3 * calcinatorQuality : (magnitude && duration ? 2 * toolQuality + calcinatorQuality : 2/3.0 * (toolQuality + calcinatorQuality) + 0.5); break; - + case 2: - + quality = negative ? 1+toolQuality : (magnitude && duration ? toolQuality : toolQuality + 0.5); break; - + case 3: - + quality = magnitude && duration ? calcinatorQuality : calcinatorQuality + 0.5; break; } @@ -110,8 +110,8 @@ void MWMechanics::Alchemy::applyTools (int flags, float& value) const else { if (quality==0) - throw std::runtime_error ("invalid derived alchemy apparatus quality"); - + throw std::runtime_error ("invalid derived alchemy apparatus quality"); + value /= quality; } } @@ -141,21 +141,21 @@ void MWMechanics::Alchemy::updateEffects() for (std::set::const_iterator iter (effects.begin()); iter!=effects.end(); ++iter) { const ESM::MagicEffect *magicEffect = - MWBase::Environment::get().getWorld()->getStore().get().find (iter->mId); - + MWBase::Environment::get().getWorld()->getStore().get().find (iter->mId); + if (magicEffect->mData.mBaseCost<=0) { std::ostringstream os; os << "invalid base cost for magic effect " << iter->mId; throw std::runtime_error (os.str()); } - + float fPotionT1MagMul = MWBase::Environment::get().getWorld()->getStore().get().find ("fPotionT1MagMult")->getFloat(); if (fPotionT1MagMul<=0) throw std::runtime_error ("invalid gmst: fPotionT1MagMul"); - + float fPotionT1DurMult = MWBase::Environment::get().getWorld()->getStore().get().find ("fPotionT1DurMult")->getFloat(); @@ -172,25 +172,25 @@ void MWMechanics::Alchemy::updateEffects() if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)) applyTools (magicEffect->mData.mFlags, duration); - - duration = static_cast (duration+0.5); + + duration = static_cast (duration+0.5); magnitude = static_cast (magnitude+0.5); if (magnitude>0 && duration>0) { ESM::ENAMstruct effect; effect.mEffectID = iter->mId; - + effect.mSkill = effect.mAttribute = iter->mArg; // somewhat hack-ish, but should work - + effect.mRange = 0; - effect.mArea = 0; - + effect.mArea = 0; + effect.mDuration = duration; effect.mMagnMin = effect.mMagnMax = magnitude; mEffects.push_back (effect); - } + } } } @@ -204,14 +204,14 @@ const ESM::Potion *MWMechanics::Alchemy::getRecord() const { if (iter->mEffects.mList.size() != mEffects.size()) continue; - - bool mismatch = false; + + bool mismatch = false; for (int i=0; i (iter->mEffects.mList.size()); ++i) { const ESM::ENAMstruct& first = iter->mEffects.mList[i]; const ESM::ENAMstruct& second = mEffects[i]; - + if (first.mEffectID!=second.mEffectID || first.mArea!=second.mArea || first.mRange!=second.mRange || @@ -225,18 +225,18 @@ const ESM::Potion *MWMechanics::Alchemy::getRecord() const break; } } - + if (!mismatch) return &(*iter); } - + return 0; } void MWMechanics::Alchemy::removeIngredients() { bool needsUpdate = false; - + for (TIngredientsContainer::iterator iter (mIngredients.begin()); iter!=mIngredients.end(); ++iter) if (!iter->isEmpty()) { @@ -248,7 +248,7 @@ void MWMechanics::Alchemy::removeIngredients() *iter = MWWorld::Ptr(); } } - + if (needsUpdate) updateEffects(); } @@ -256,37 +256,37 @@ void MWMechanics::Alchemy::removeIngredients() void MWMechanics::Alchemy::addPotion (const std::string& name) { const ESM::Potion *record = getRecord(); - + if (!record) { ESM::Potion newRecord; - + newRecord.mData.mWeight = 0; - + for (TIngredientsIterator iter (beginIngredients()); iter!=endIngredients(); ++iter) if (!iter->isEmpty()) newRecord.mData.mWeight += iter->get()->mBase->mData.mWeight; - + newRecord.mData.mWeight /= countIngredients(); - + newRecord.mData.mValue = mValue; newRecord.mData.mAutoCalc = 0; - + newRecord.mName = name; - int index = static_cast (std::rand()/static_cast (RAND_MAX+1)*6); + int index = static_cast (std::rand()/(static_cast (RAND_MAX)+1)*6); assert (index>=0 && index<6); - + static const char *name[] = { "standard", "bargain", "cheap", "fresh", "exclusive", "quality" }; - + newRecord.mModel = "m\\misc_potion_" + std::string (name[index]) + "_01.nif"; newRecord.mIcon = "m\\tx_potion_" + std::string (name[index]) + "_01.dds"; - + newRecord.mEffects.mList = mEffects; - + record = MWBase::Environment::get().getWorld()->createRecord (newRecord); } - + MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), record->mId); MWWorld::Class::get (mAlchemist).getContainerStore (mAlchemist).add (ref.getPtr(), mAlchemist); } @@ -300,7 +300,7 @@ float MWMechanics::Alchemy::getChance() const { const CreatureStats& creatureStats = MWWorld::Class::get (mAlchemist).getCreatureStats (mAlchemist); const NpcStats& npcStats = MWWorld::Class::get (mAlchemist).getNpcStats (mAlchemist); - + return (npcStats.getSkill (ESM::Skill::Alchemy).getModified() + 0.1 * creatureStats.getAttribute (1).getModified() @@ -321,34 +321,34 @@ int MWMechanics::Alchemy::countIngredients() const void MWMechanics::Alchemy::setAlchemist (const MWWorld::Ptr& npc) { mAlchemist = npc; - + mIngredients.resize (4); std::fill (mIngredients.begin(), mIngredients.end(), MWWorld::Ptr()); - + mTools.resize (4); - + std::fill (mTools.begin(), mTools.end(), MWWorld::Ptr()); - + mEffects.clear(); - + MWWorld::ContainerStore& store = MWWorld::Class::get (npc).getContainerStore (npc); - + for (MWWorld::ContainerStoreIterator iter (store.begin (MWWorld::ContainerStore::Type_Apparatus)); iter!=store.end(); ++iter) - { + { MWWorld::LiveCellRef* ref = iter->get(); - + int type = ref->mBase->mData.mType; - + if (type<0 || type>=static_cast (mTools.size())) throw std::runtime_error ("invalid apparatus type"); - + if (!mTools[type].isEmpty()) if (ref->mBase->mData.mQuality<=mTools[type].get()->mBase->mData.mQuality) continue; - - mTools[type] = *iter; + + mTools[type] = *iter; } } @@ -390,19 +390,19 @@ int MWMechanics::Alchemy::addIngredient (const MWWorld::Ptr& ingredient) { slot = i; break; - } - + } + if (slot==-1) return -1; - + for (TIngredientsIterator iter (mIngredients.begin()); iter!=mIngredients.end(); ++iter) if (!iter->isEmpty() && ingredient.get()==iter->get()) return -1; - + mIngredients[slot] = ingredient; - + updateEffects(); - + return slot; } @@ -429,7 +429,7 @@ std::string MWMechanics::Alchemy::getPotionName() const { if (const ESM::Potion *potion = getRecord()) return potion->mName; - + return ""; } @@ -437,13 +437,13 @@ MWMechanics::Alchemy::Result MWMechanics::Alchemy::create (const std::string& na { if (mTools[ESM::Apparatus::MortarPestle].isEmpty()) return Result_NoMortarAndPestle; - + if (countIngredients()<2) return Result_LessThanTwoIngredients; if (name.empty() && getPotionName().empty()) return Result_NoName; - + if (beginEffects()==endEffects()) return Result_NoEffects; @@ -456,7 +456,7 @@ MWMechanics::Alchemy::Result MWMechanics::Alchemy::create (const std::string& na addPotion (name); removeIngredients(); - + increaseSkill(); return Result_Success; From afafaf73e87fc0e16c8e914472cd94643e6c94af Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 10 Nov 2013 22:40:46 +0100 Subject: [PATCH 232/434] Fix a build error --- components/contentselector/model/contentmodel.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 5f3575eb40..9183003295 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -1,6 +1,8 @@ #include "contentmodel.hpp" #include "esmfile.hpp" +#include + #include #include #include From 6f3d7374985d3a915b1d52d3e97a54419d4016f7 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 10 Nov 2013 23:21:26 +0100 Subject: [PATCH 233/434] Missing const, thanks to jeaye --- apps/openmw/mwgui/spellicons.cpp | 2 +- apps/openmw/mwmechanics/creaturestats.cpp | 4 ++-- apps/openmw/mwmechanics/creaturestats.hpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwgui/spellicons.cpp b/apps/openmw/mwgui/spellicons.cpp index dfbcfc2fff..aa2474fa52 100644 --- a/apps/openmw/mwgui/spellicons.cpp +++ b/apps/openmw/mwgui/spellicons.cpp @@ -120,7 +120,7 @@ namespace MWGui MWBase::Environment::get().getWorld ()->getStore ().get().find(effectIt->mEffectID); MagicEffectInfo effectInfo; - if (it->second.mName != "") + if (!it->second.mName.empty()) effectInfo.mSource = it->second.mName; else effectInfo.mSource = getSpellDisplayName(it->first); diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index c0450c3443..345b5a1563 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -343,9 +343,9 @@ namespace MWMechanics return mLastHitObject; } - bool CreatureStats::canUsePower(const std::string &power) + bool CreatureStats::canUsePower(const std::string &power) const { - std::map::iterator it = mUsedPowers.find(power); + std::map::const_iterator it = mUsedPowers.find(power); if (it == mUsedPowers.end() || it->second + 24 <= MWBase::Environment::get().getWorld()->getTimeStamp()) return true; else diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index 989f1d48c5..126b0685f5 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -49,7 +49,7 @@ namespace MWMechanics public: CreatureStats(); - bool canUsePower (const std::string& power); + bool canUsePower (const std::string& power) const; void usePower (const std::string& power); const Stat & getAttribute(int index) const; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 1cda204e59..5747c636f4 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2030,7 +2030,7 @@ namespace MWWorld stats.setAttackingOrSpell(false); std::string selectedSpell = stats.getSpells().getSelectedSpell(); - std::string sourceName = ""; + std::string sourceName; if (!selectedSpell.empty()) { const ESM::Spell* spell = getStore().get().search(selectedSpell); From 2fff7fc8436eafb12813c1be705956f940f6fffe Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 11 Nov 2013 12:21:26 +0100 Subject: [PATCH 234/434] save info records --- apps/opencs/model/doc/saving.cpp | 7 +- apps/opencs/model/doc/savingstages.cpp | 113 +++++++++++++++++++++++++ apps/opencs/model/doc/savingstages.hpp | 33 +++++++- components/esm/loadinfo.cpp | 1 - 4 files changed, 146 insertions(+), 8 deletions(-) diff --git a/apps/opencs/model/doc/saving.cpp b/apps/opencs/model/doc/saving.cpp index b756a9dc19..73278ba975 100644 --- a/apps/opencs/model/doc/saving.cpp +++ b/apps/opencs/model/doc/saving.cpp @@ -58,12 +58,9 @@ CSMDoc::Saving::Saving (Document& document, const boost::filesystem::path& proje appendStage (new WriteCollectionStage > (mDocument.getData().getSpells(), mState)); - /// \todo deal with info records for topcis and journals - appendStage (new WriteCollectionStage > - (mDocument.getData().getTopics(), mState)); + appendStage (new WriteDialogueCollectionStage (mDocument, mState, false)); - appendStage (new WriteCollectionStage > - (mDocument.getData().getJournals(), mState)); + appendStage (new WriteDialogueCollectionStage (mDocument, mState, true)); appendStage (new WriteRefIdCollectionStage (mDocument, mState)); diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index d68c723179..8e9bcfc0de 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -7,6 +7,10 @@ #include +#include + +#include "../world/infocollection.hpp" + #include "document.hpp" #include "savingstate.hpp" @@ -80,6 +84,115 @@ void CSMDoc::WriteHeaderStage::perform (int stage, std::vector& mes } +CSMDoc::WriteDialogueCollectionStage::WriteDialogueCollectionStage (Document& document, + SavingState& state, bool journal) +: mDocument (document), mState (state), + mTopics (journal ? document.getData().getJournals() : document.getData().getTopics()), + mInfos (journal ? document.getData().getJournalInfos() : document.getData().getTopicInfos()) +{} + +int CSMDoc::WriteDialogueCollectionStage::setup() +{ + return mTopics.getSize(); +} + +void CSMDoc::WriteDialogueCollectionStage::perform (int stage, std::vector& messages) +{ + const CSMWorld::Record& topic = mTopics.getRecord (stage); + + CSMWorld::RecordBase::State state = topic.mState; + + if (state==CSMWorld::RecordBase::State_Deleted) + { + // if the topic is deleted, we do not need to bother with INFO records. + + /// \todo wrote record with delete flag + + return; + } + + // Test, if we need to save anything associated info records. + bool infoModified = false; + + CSMWorld::InfoCollection::Range range = mInfos.getTopicRange (topic.get().mId); + + for (CSMWorld::InfoCollection::RecordConstIterator iter (range.first); iter!=range.second; ++iter) + { + CSMWorld::RecordBase::State state = iter->mState; + + if (state==CSMWorld::RecordBase::State_Modified || + state==CSMWorld::RecordBase::State_ModifiedOnly || + state==CSMWorld::RecordBase::State_Deleted) + { + infoModified = true; + break; + } + } + + if (state==CSMWorld::RecordBase::State_Modified || + state==CSMWorld::RecordBase::State_ModifiedOnly || + infoModified) + { + // always write the topic record + std::string type; + for (int i=0; i<4; ++i) + /// \todo make endianess agnostic (change ESMWriter interface?) + type += reinterpret_cast (&topic.mModified.sRecordId)[i]; + + mState.getWriter().startRecord (type); + mState.getWriter().writeHNCString ("NAME", topic.mModified.mId); + topic.mModified.save (mState.getWriter()); + mState.getWriter().endRecord (type); + + // write modified selected info records + for (CSMWorld::InfoCollection::RecordConstIterator iter (range.first); iter!=range.second; + ++iter) + { + CSMWorld::RecordBase::State state = iter->mState; + + if (state==CSMWorld::RecordBase::State_Deleted) + { + /// \todo wrote record with delete flag + } + else if (state==CSMWorld::RecordBase::State_Modified || + state==CSMWorld::RecordBase::State_ModifiedOnly) + { + ESM::DialInfo info = iter->get(); + info.mId = info.mId.substr (info.mId.find_last_of ('#')+1); + + if (iter!=range.first) + { + CSMWorld::InfoCollection::RecordConstIterator prev = iter; + --prev; + + info.mPrev = + prev->mModified.mId.substr (prev->mModified.mId.find_last_of ('#')+1); + } + + CSMWorld::InfoCollection::RecordConstIterator next = iter; + ++next; + + if (next!=range.second) + { + info.mNext = + next->mModified.mId.substr (next->mModified.mId.find_last_of ('#')+1); + } + + std::string type; + for (int i=0; i<4; ++i) + /// \todo make endianess agnostic (change ESMWriter interface?) + type += reinterpret_cast (&info.sRecordId)[i]; + + mState.getWriter().startRecord (type); + mState.getWriter().writeHNCString ("INAM", info.mId); + info.save (mState.getWriter()); + mState.getWriter().endRecord (type); + } + } + } +} + + CSMDoc::WriteRefIdCollectionStage::WriteRefIdCollectionStage (Document& document, SavingState& state) : mDocument (document), mState (state) {} diff --git a/apps/opencs/model/doc/savingstages.hpp b/apps/opencs/model/doc/savingstages.hpp index ff94116fdb..ca5586511d 100644 --- a/apps/opencs/model/doc/savingstages.hpp +++ b/apps/opencs/model/doc/savingstages.hpp @@ -3,13 +3,23 @@ #include "stage.hpp" -#include "savingstate.hpp" - #include "../world/record.hpp" #include "../world/idcollection.hpp" #include "../filter/filter.hpp" +#include "savingstate.hpp" + +namespace ESM +{ + struct Dialogue; +} + +namespace CSMWorld +{ + class InfoCollection; +} + namespace CSMDoc { class Document; @@ -106,6 +116,25 @@ namespace CSMDoc } + class WriteDialogueCollectionStage : public Stage + { + Document& mDocument; + SavingState& mState; + const CSMWorld::IdCollection& mTopics; + CSMWorld::InfoCollection& mInfos; + + public: + + WriteDialogueCollectionStage (Document& document, SavingState& state, bool journal); + + virtual int setup(); + ///< \return number of steps + + virtual void perform (int stage, std::vector& messages); + ///< Messages resulting from this stage will be appended to \a messages. + }; + + class WriteRefIdCollectionStage : public Stage { Document& mDocument; diff --git a/components/esm/loadinfo.cpp b/components/esm/loadinfo.cpp index 5120a0043c..f86ad3b51b 100644 --- a/components/esm/loadinfo.cpp +++ b/components/esm/loadinfo.cpp @@ -123,7 +123,6 @@ void DialInfo::load(ESMReader &esm) void DialInfo::save(ESMWriter &esm) const { - esm.writeHNCString("INAM", mId); esm.writeHNCString("PNAM", mPrev); esm.writeHNCString("NNAM", mNext); esm.writeHNT("DATA", mData, 12); From ffdb91bb21be8953a60013fa6bf8d4428d0eff17 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 11 Nov 2013 23:43:28 +0100 Subject: [PATCH 235/434] Add particles for Cast + Hit. Not looking quite right yet. --- apps/openmw/mwmechanics/activespells.cpp | 30 ++++++++++++------ apps/openmw/mwmechanics/character.cpp | 3 ++ apps/openmw/mwrender/animation.cpp | 39 ++++++++++++++++++++++++ apps/openmw/mwrender/animation.hpp | 19 ++++++++++++ apps/openmw/mwworld/worldimp.cpp | 6 +++- components/esm/loadmgef.hpp | 4 +-- components/nifogre/ogrenifloader.cpp | 26 +++++++++++++--- components/nifogre/ogrenifloader.hpp | 5 ++- 8 files changed, 113 insertions(+), 19 deletions(-) diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index c433ac5e78..666a147751 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -17,6 +17,8 @@ #include "../mwbase/world.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwrender/animation.hpp" + #include "../mwworld/class.hpp" #include "creaturestats.hpp" @@ -217,7 +219,8 @@ namespace MWMechanics else iter->second = params; - // Play sounds + // Play sounds & particles + bool first=true; for (std::vector::const_iterator iter (effects.first.mList.begin()); iter!=effects.first.mList.end(); ++iter) { @@ -228,17 +231,24 @@ namespace MWMechanics MWBase::Environment::get().getWorld()->getStore().get().find ( iter->mEffectID); - static const std::string schools[] = { - "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" - }; + // Only the sound of the first effect plays + if (first) + { + static const std::string schools[] = { + "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" + }; - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - if(!magicEffect->mHitSound.empty()) - sndMgr->playSound3D(actor, magicEffect->mHitSound, 1.0f, 1.0f); - else - sndMgr->playSound3D(actor, schools[magicEffect->mData.mSchool]+" hit", 1.0f, 1.0f); + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + if(!magicEffect->mHitSound.empty()) + sndMgr->playSound3D(actor, magicEffect->mHitSound, 1.0f, 1.0f); + else + sndMgr->playSound3D(actor, schools[magicEffect->mData.mSchool]+" hit", 1.0f, 1.0f); + } - break; + const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get().find (magicEffect->mHit); + MWBase::Environment::get().getWorld()->getAnimation(actor)->addEffect("meshes\\" + castStatic->mModel, ""); + + first = false; } mSpellsChanged = true; diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 8c88b3ad03..fe1254b43a 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -512,6 +512,9 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun const ESM::MagicEffect *effect; effect = store.get().find(effectentry.mEffectID); + const ESM::Static* castStatic = store.get().find (effect->mCasting); + mAnimation->addEffect("meshes\\" + castStatic->mModel, ""); + switch(effectentry.mRange) { case 0: mAttackType = "self"; break; diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 53d697fdd9..d494304d19 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -39,6 +39,15 @@ void Animation::AnimationValue::setValue(Ogre::Real) { } +Ogre::Real Animation::EffectAnimationValue::getValue() const +{ + return mTime; +} + +void Animation::EffectAnimationValue::setValue(Ogre::Real) +{ +} + void Animation::destroyObjectList(Ogre::SceneManager *sceneMgr, NifOgre::ObjectList &objects) { @@ -916,6 +925,25 @@ Ogre::Vector3 Animation::runAnimation(float duration) mSkelBase->getAllAnimationStates()->_notifyDirty(); } + + for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ) + { + for(size_t i = 0; i < it->mControllers.size() ;i++) + { + static_cast (it->mControllers[i].getSource().get())->addTime(duration); + + it->mControllers[i].update(); + } + + if (it->mControllers[0].getSource()->getValue() >= it->mMaxControllerLength) + { + destroyObjectList(mInsert->getCreator(), *it); + it = mEffects.erase(it); + } + else + ++it; + } + return movement; } @@ -980,6 +1008,17 @@ void Animation::detachObjectFromBone(Ogre::MovableObject *obj) mSkelBase->detachObjectFromBone(obj); } +void Animation::addEffect(const std::string &model, const std::string &bonename) +{ + NifOgre::ObjectList list = NifOgre::Loader::createObjects(mInsert, model); + for(size_t i = 0;i < list.mControllers.size();i++) + { + if(list.mControllers[i].getSource().isNull()) + list.mControllers[i].setSource(Ogre::SharedPtr (new EffectAnimationValue())); + } + mEffects.push_back(list); +} + ObjectAnimation::ObjectAnimation(const MWWorld::Ptr& ptr, const std::string &model) : Animation(ptr, ptr.getRefData().getBaseNode()) diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 2215fc5827..efc7a6a59a 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -3,6 +3,7 @@ #include #include +#include #include @@ -52,6 +53,19 @@ protected: virtual void setValue(Ogre::Real value); }; + class EffectAnimationValue : public Ogre::ControllerValue + { + private: + float mTime; + public: + EffectAnimationValue() : mTime(0) { } + void addTime(float time) { mTime += time; } + + virtual Ogre::Real getValue() const; + virtual void setValue(Ogre::Real value); + }; + + class NullAnimationValue : public Ogre::ControllerValue { @@ -95,6 +109,8 @@ protected: typedef std::map ObjectAttachMap; + std::vector mEffects; + MWWorld::Ptr mPtr; Camera *mCamera; @@ -114,6 +130,7 @@ protected: ObjectAttachMap mAttachedObjects; + /* Sets the appropriate animations on the bone groups based on priority. */ void resetActiveGroups(); @@ -173,6 +190,8 @@ public: Animation(const MWWorld::Ptr &ptr, Ogre::SceneNode *node); virtual ~Animation(); + void addEffect (const std::string& model, const std::string& bonename = ""); + void updatePtr(const MWWorld::Ptr &ptr); bool hasAnimation(const std::string &anim); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 5747c636f4..254c7e1bee 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2029,6 +2029,8 @@ namespace MWWorld MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); stats.setAttackingOrSpell(false); + ESM::EffectList effects; + std::string selectedSpell = stats.getSpells().getSelectedSpell(); std::string sourceName; if (!selectedSpell.empty()) @@ -2094,6 +2096,7 @@ namespace MWWorld actor.getClass().skillUsageSucceeded(actor, MWMechanics::spellSchoolToSkill(MWMechanics::getSpellSchool(selectedSpell, actor)), 0); + effects = spell->mEffects; } InventoryStore& inv = actor.getClass().getInventoryStore(actor); if (selectedSpell.empty() && inv.getSelectedEnchantItem() != inv.end()) @@ -2137,6 +2140,8 @@ namespace MWWorld } else MWBase::Environment::get().getWindowManager()->setSelectedEnchantItem(item); // Set again to show the modified charge + + effects = enchantment->mEffects; } // Now apply the spell! @@ -2158,7 +2163,6 @@ namespace MWWorld } } - // TODO: Launch projectile if there's a Target portion } void World::updateAnimParts(const Ptr& actor) diff --git a/components/esm/loadmgef.hpp b/components/esm/loadmgef.hpp index 9d7397a341..d18507b100 100644 --- a/components/esm/loadmgef.hpp +++ b/components/esm/loadmgef.hpp @@ -64,8 +64,8 @@ struct MagicEffect MEDTstruct mData; std::string mIcon, mParticle; // Textures - std::string mCasting, mHit, mArea; // Statics - std::string mBolt; // Weapon + std::string mCasting, mHit, mArea; // ESM::Static + std::string mBolt; // ESM::Weapon std::string mCastSound, mBoltSound, mHitSound, mAreaSound; // Sounds std::string mDescription; diff --git a/components/nifogre/ogrenifloader.cpp b/components/nifogre/ogrenifloader.cpp index 3bb9ea2309..4b9cb5ad5b 100644 --- a/components/nifogre/ogrenifloader.cpp +++ b/components/nifogre/ogrenifloader.cpp @@ -54,6 +54,7 @@ private: float mFrequency; float mPhase; float mStartTime; +public: float mStopTime; public: @@ -468,7 +469,10 @@ class NIFObjectLoader Ogre::ControllerManager::getSingleton().getFrameTimeSource() : Ogre::ControllerValueRealPtr()); Ogre::ControllerValueRealPtr dstval(OGRE_NEW UVController::Value(material, uv->data.getPtr())); - Ogre::ControllerFunctionRealPtr func(OGRE_NEW UVController::Function(uv, (animflags&Nif::NiNode::AnimFlag_AutoPlay))); + + UVController::Function* function = OGRE_NEW UVController::Function(uv, (animflags&Nif::NiNode::AnimFlag_AutoPlay)); + objectlist.mMaxControllerLength = std::max(function->mStopTime, objectlist.mMaxControllerLength); + Ogre::ControllerFunctionRealPtr func(function); objectlist.mControllers.push_back(Ogre::Controller(srcval, dstval, func)); } @@ -481,7 +485,10 @@ class NIFObjectLoader Ogre::ControllerManager::getSingleton().getFrameTimeSource() : Ogre::ControllerValueRealPtr()); Ogre::ControllerValueRealPtr dstval(OGRE_NEW GeomMorpherController::Value(subent, geom->data.getPtr())); - Ogre::ControllerFunctionRealPtr func(OGRE_NEW GeomMorpherController::Function(geom, (animflags&Nif::NiNode::AnimFlag_AutoPlay))); + + GeomMorpherController::Function* function = OGRE_NEW GeomMorpherController::Function(geom, (animflags&Nif::NiNode::AnimFlag_AutoPlay)); + objectlist.mMaxControllerLength = std::max(function->mStopTime, objectlist.mMaxControllerLength); + Ogre::ControllerFunctionRealPtr func(function); objectlist.mControllers.push_back(Ogre::Controller(srcval, dstval, func)); } @@ -616,7 +623,11 @@ class NIFObjectLoader Ogre::ControllerManager::getSingleton().getFrameTimeSource() : Ogre::ControllerValueRealPtr()); Ogre::ControllerValueRealPtr dstval(OGRE_NEW ParticleSystemController::Value(partsys, partctrl)); - Ogre::ControllerFunctionRealPtr func(OGRE_NEW ParticleSystemController::Function(partctrl, (partflags&Nif::NiNode::ParticleFlag_AutoPlay))); + + ParticleSystemController::Function* function = + OGRE_NEW ParticleSystemController::Function(partctrl, (partflags&Nif::NiNode::ParticleFlag_AutoPlay)); + objectlist.mMaxControllerLength = std::max(function->mStopTime, objectlist.mMaxControllerLength); + Ogre::ControllerFunctionRealPtr func(function); objectlist.mControllers.push_back(Ogre::Controller(srcval, dstval, func)); } @@ -648,7 +659,10 @@ class NIFObjectLoader Ogre::ControllerManager::getSingleton().getFrameTimeSource() : Ogre::ControllerValueRealPtr()); Ogre::ControllerValueRealPtr dstval(OGRE_NEW VisController::Value(trgtbone, vis->data.getPtr())); - Ogre::ControllerFunctionRealPtr func(OGRE_NEW VisController::Function(vis, (animflags&Nif::NiNode::AnimFlag_AutoPlay))); + + VisController::Function* function = OGRE_NEW VisController::Function(vis, (animflags&Nif::NiNode::AnimFlag_AutoPlay)); + objectlist.mMaxControllerLength = std::max(function->mStopTime, objectlist.mMaxControllerLength); + Ogre::ControllerFunctionRealPtr func(function); objectlist.mControllers.push_back(Ogre::Controller(srcval, dstval, func)); } @@ -663,7 +677,9 @@ class NIFObjectLoader Ogre::ControllerManager::getSingleton().getFrameTimeSource() : Ogre::ControllerValueRealPtr()); Ogre::ControllerValueRealPtr dstval(OGRE_NEW KeyframeController::Value(trgtbone, key->data.getPtr())); - Ogre::ControllerFunctionRealPtr func(OGRE_NEW KeyframeController::Function(key, (animflags&Nif::NiNode::AnimFlag_AutoPlay))); + KeyframeController::Function* function = OGRE_NEW KeyframeController::Function(key, (animflags&Nif::NiNode::AnimFlag_AutoPlay)); + objectlist.mMaxControllerLength = std::max(function->mStopTime, objectlist.mMaxControllerLength); + Ogre::ControllerFunctionRealPtr func(function); objectlist.mControllers.push_back(Ogre::Controller(srcval, dstval, func)); } diff --git a/components/nifogre/ogrenifloader.hpp b/components/nifogre/ogrenifloader.hpp index edad13a9a3..de06dd3d50 100644 --- a/components/nifogre/ogrenifloader.hpp +++ b/components/nifogre/ogrenifloader.hpp @@ -45,11 +45,14 @@ struct ObjectList { std::vector mParticles; std::vector mLights; + // The maximum length on any of the controllers. For animations with controllers, but no text keys, consider this the animation length. + float mMaxControllerLength; + std::map mTextKeys; std::vector > mControllers; - ObjectList() : mSkelBase(0) + ObjectList() : mSkelBase(0), mMaxControllerLength(0) { } }; From 5f209b120b0278a57d4ac12e8925eff6018abfa4 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 12 Nov 2013 01:26:16 +0100 Subject: [PATCH 236/434] Use the new ContainerStore::remove method. --- apps/openmw/mwworld/worldimp.cpp | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 254c7e1bee..e42d590b5c 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2127,20 +2127,18 @@ namespace MWWorld } if (enchantment->mData.mType == ESM::Enchantment::CastOnce) { - item.getRefData().setCount(item.getRefData().getCount()-1); - } - - sourceName = item.getClass().getName(item); - - if (!item.getRefData().getCount()) - { - // Item was used up - MWBase::Environment::get().getWindowManager()->unsetSelectedSpell(); - inv.setSelectedEnchantItem(inv.end()); + if (!item.getContainerStore()->remove(item, 1, actor)) + { + // Item was used up + MWBase::Environment::get().getWindowManager()->unsetSelectedSpell(); + inv.setSelectedEnchantItem(inv.end()); + } } else MWBase::Environment::get().getWindowManager()->setSelectedEnchantItem(item); // Set again to show the modified charge + sourceName = item.getClass().getName(item); + effects = enchantment->mEffects; } From eccb8f38ba81c52f48d91126b5378d94c0057585 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 12 Nov 2013 02:07:51 +0100 Subject: [PATCH 237/434] Apply "cast when strikes" enchantments when hitting with a weapon --- apps/openmw/mwclass/npc.cpp | 30 ++++++++++++++++++++++++ apps/openmw/mwmechanics/activespells.cpp | 7 ++++-- apps/openmw/mwworld/worldimp.cpp | 4 +++- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 01a0c0a6f9..5fb3591ad2 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -444,6 +444,36 @@ namespace MWClass if(ptr.getRefData().getHandle() == "player") skillUsageSucceeded(ptr, weapskill, 0); + // Apply "On hit" enchanted weapons + std::string enchantmentName = weapon.getClass().getEnchantment(weapon); + if (!enchantmentName.empty()) + { + const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find( + enchantmentName); + if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes) + { + // Check if we have enough charges + const float enchantCost = enchantment->mData.mCost; + int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified(); + const float castCost = enchantCost - (enchantCost / 100) * (eSkill - 10); + + if (weapon.getCellRef().mEnchantmentCharge == -1) + weapon.getCellRef().mEnchantmentCharge = enchantment->mData.mCharge; + if (weapon.getCellRef().mEnchantmentCharge < castCost) + { + if (ptr.getRefData().getHandle() == "player") + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientCharge}"); + } + else + { + weapon.getCellRef().mEnchantmentCharge -= castCost; + othercls.getCreatureStats(victim).getActiveSpells().addSpell(enchantmentName, victim, ESM::RT_Touch, weapon.getClass().getName(weapon)); + getCreatureStats(ptr).getActiveSpells().addSpell(enchantmentName, ptr, ESM::RT_Self, weapon.getClass().getName(weapon)); + // TODO: RT_Target + } + } + } + othercls.onHit(victim, damage, healthdmg, weapon, ptr, true); } diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index 666a147751..9e0ddb92b9 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -245,8 +245,11 @@ namespace MWMechanics sndMgr->playSound3D(actor, schools[magicEffect->mData.mSchool]+" hit", 1.0f, 1.0f); } - const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get().find (magicEffect->mHit); - MWBase::Environment::get().getWorld()->getAnimation(actor)->addEffect("meshes\\" + castStatic->mModel, ""); + if (!magicEffect->mHit.empty()) + { + const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get().find (magicEffect->mHit); + MWBase::Environment::get().getWorld()->getAnimation(actor)->addEffect("meshes\\" + castStatic->mModel, ""); + } first = false; } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index e42d590b5c..e343546c20 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2094,7 +2094,9 @@ namespace MWWorld return; } - actor.getClass().skillUsageSucceeded(actor, MWMechanics::spellSchoolToSkill(MWMechanics::getSpellSchool(selectedSpell, actor)), 0); + if (actor == getPlayer().getPlayer()) + actor.getClass().skillUsageSucceeded(actor, + MWMechanics::spellSchoolToSkill(MWMechanics::getSpellSchool(selectedSpell, actor)), 0); effects = spell->mEffects; } From 0dab7031c025ed6fc19d881a4c7263429ba8f203 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 12 Nov 2013 02:13:36 +0100 Subject: [PATCH 238/434] Make sure effects are destroyed with the animation --- apps/openmw/mwclass/npc.cpp | 2 +- apps/openmw/mwrender/animation.cpp | 3 +++ apps/openmw/mwrender/animation.hpp | 1 - 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 5fb3591ad2..492157993d 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -445,7 +445,7 @@ namespace MWClass skillUsageSucceeded(ptr, weapskill, 0); // Apply "On hit" enchanted weapons - std::string enchantmentName = weapon.getClass().getEnchantment(weapon); + std::string enchantmentName = !weapon.isEmpty() ? weapon.getClass().getEnchantment(weapon) : ""; if (!enchantmentName.empty()) { const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find( diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index d494304d19..6ef51945eb 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -87,6 +87,9 @@ Animation::Animation(const MWWorld::Ptr &ptr, Ogre::SceneNode *node) Animation::~Animation() { + for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ++it) + destroyObjectList(mInsert->getCreator(), *it); + mAnimSources.clear(); Ogre::SceneManager *sceneMgr = mInsert->getCreator(); diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index efc7a6a59a..6653b342ff 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -3,7 +3,6 @@ #include #include -#include #include From 700d06764cb521f10d0f17e786c64c5435e89295 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 12 Nov 2013 22:58:56 +0100 Subject: [PATCH 239/434] Order of arguments for ContainerStore::stacks shouldn't matter. Supplying them in the correct order is error prone, and also caused a bug where equipped items would incorrectly stack. --- apps/openmw/mwgui/inventoryitemmodel.cpp | 2 +- apps/openmw/mwworld/containerstore.hpp | 1 - apps/openmw/mwworld/inventorystore.cpp | 2 +- apps/openmw/mwworld/inventorystore.hpp | 1 - 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwgui/inventoryitemmodel.cpp b/apps/openmw/mwgui/inventoryitemmodel.cpp index 712e1b6c64..672ea9c16f 100644 --- a/apps/openmw/mwgui/inventoryitemmodel.cpp +++ b/apps/openmw/mwgui/inventoryitemmodel.cpp @@ -56,7 +56,7 @@ void InventoryItemModel::removeItem (const ItemStack& item, size_t count) if (removed == 0) throw std::runtime_error("Item to remove not found in container store"); - else if (removed < count) + else if (removed < static_cast(count)) throw std::runtime_error("Not enough items in the stack to remove"); } diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index 2cdefd2f4b..7278135d82 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -93,7 +93,6 @@ namespace MWWorld virtual bool stacks (const Ptr& stack, const Ptr& item); ///< @return true if the two specified objects can stack with each other - /// @note stack is the item that is already in this container void fill (const ESM::InventoryList& items, const std::string& owner, const MWWorld::ESMStore& store); ///< Insert items into *this. diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index dc2d0cab13..4302180583 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -295,7 +295,7 @@ bool MWWorld::InventoryStore::stacks(const Ptr& stack, const Ptr& item) for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter) { - if (*iter != end() && stack == **iter) + if (*iter != end() && (stack == **iter || item == **iter)) { bool stackWhenEquipped = MWWorld::Class::get(**iter).getEquipmentSlots(**iter).second; if (!stackWhenEquipped) diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index c4ad517776..a974c0dae0 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -109,7 +109,6 @@ namespace MWWorld virtual bool stacks (const Ptr& stack, const Ptr& item); ///< @return true if the two specified objects can stack with each other - /// @note stack is the item that is already in this container (it may be equipped) virtual int remove(const Ptr& item, int count, const Ptr& actor); ///< Remove \a count item(s) designated by \a item from this inventory. From 3c6a3915076f43683b80606415a300463362e04c Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 12 Nov 2013 23:12:56 +0100 Subject: [PATCH 240/434] Rename arguments again --- apps/openmw/mwworld/containerstore.cpp | 30 +++++++++++++------------- apps/openmw/mwworld/containerstore.hpp | 2 +- apps/openmw/mwworld/inventorystore.cpp | 6 +++--- apps/openmw/mwworld/inventorystore.hpp | 2 +- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index c041beffa6..4639a1d2da 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -77,31 +77,31 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::end() return ContainerStoreIterator (this); } -bool MWWorld::ContainerStore::stacks(const Ptr& stack, const Ptr& item) +bool MWWorld::ContainerStore::stacks(const Ptr& ptr1, const Ptr& ptr2) { - const MWWorld::Class& cls1 = MWWorld::Class::get(stack); - const MWWorld::Class& cls2 = MWWorld::Class::get(item); + const MWWorld::Class& cls1 = MWWorld::Class::get(ptr1); + const MWWorld::Class& cls2 = MWWorld::Class::get(ptr2); /// \todo add current enchantment charge here when it is implemented - return stack != item // an item never stacks onto itself - && Misc::StringUtils::ciEqual(stack.getCellRef().mRefID, item.getCellRef().mRefID) - && stack.getCellRef().mOwner == item.getCellRef().mOwner - && stack.getCellRef().mSoul == item.getCellRef().mSoul + return ptr1 != ptr2 // an item never stacks onto itself + && Misc::StringUtils::ciEqual(ptr1.getCellRef().mRefID, ptr2.getCellRef().mRefID) + && ptr1.getCellRef().mOwner == ptr2.getCellRef().mOwner + && ptr1.getCellRef().mSoul == ptr2.getCellRef().mSoul // item with a script never stacks - && cls1.getScript(stack) == "" - && cls2.getScript(item) == "" + && cls1.getScript(ptr1) == "" + && cls2.getScript(ptr2) == "" // item with enchantment never stacks (we could revisit this later, // but for now it makes selecting items in the spell window much easier) - && cls1.getEnchantment(stack) == "" - && cls2.getEnchantment(item) == "" + && cls1.getEnchantment(ptr1) == "" + && cls2.getEnchantment(ptr2) == "" // item that is already partly used up never stacks - && (!cls1.hasItemHealth(stack) || stack.getCellRef().mCharge == -1 - || cls1.getItemMaxHealth(stack) == stack.getCellRef().mCharge) - && (!cls2.hasItemHealth(item) || item.getCellRef().mCharge == -1 - || cls2.getItemMaxHealth(item) == item.getCellRef().mCharge); + && (!cls1.hasItemHealth(ptr1) || ptr1.getCellRef().mCharge == -1 + || cls1.getItemMaxHealth(ptr1) == ptr1.getCellRef().mCharge) + && (!cls2.hasItemHealth(ptr2) || ptr2.getCellRef().mCharge == -1 + || cls2.getItemMaxHealth(ptr2) == ptr2.getCellRef().mCharge); } MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr, const Ptr& actorPtr) diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index 7278135d82..ca6609ecf2 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -91,7 +91,7 @@ namespace MWWorld public: - virtual bool stacks (const Ptr& stack, const Ptr& item); + virtual bool stacks (const Ptr& ptr1, const Ptr& ptr2); ///< @return true if the two specified objects can stack with each other void fill (const ESM::InventoryList& items, const std::string& owner, const MWWorld::ESMStore& store); diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 4302180583..7bfc11d846 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -285,9 +285,9 @@ void MWWorld::InventoryStore::flagAsModified() mMagicEffectsUpToDate = false; } -bool MWWorld::InventoryStore::stacks(const Ptr& stack, const Ptr& item) +bool MWWorld::InventoryStore::stacks(const Ptr& ptr1, const Ptr& ptr2) { - bool canStack = MWWorld::ContainerStore::stacks(stack, item); + bool canStack = MWWorld::ContainerStore::stacks(ptr1, ptr2); if (!canStack) return false; @@ -295,7 +295,7 @@ bool MWWorld::InventoryStore::stacks(const Ptr& stack, const Ptr& item) for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter) { - if (*iter != end() && (stack == **iter || item == **iter)) + if (*iter != end() && (ptr1 == **iter || ptr2 == **iter)) { bool stackWhenEquipped = MWWorld::Class::get(**iter).getEquipmentSlots(**iter).second; if (!stackWhenEquipped) diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index a974c0dae0..b7d3748242 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -107,7 +107,7 @@ namespace MWWorld ///< \attention This function is internal to the world model and should not be called from /// outside. - virtual bool stacks (const Ptr& stack, const Ptr& item); + virtual bool stacks (const Ptr& ptr1, const Ptr& ptr2); ///< @return true if the two specified objects can stack with each other virtual int remove(const Ptr& item, int count, const Ptr& actor); From 027f4152e415cefecd2149b8fa72c19dfc807858 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 12 Nov 2013 23:17:03 +0100 Subject: [PATCH 241/434] Missing flagAsModified() --- apps/openmw/mwworld/containerstore.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index 4639a1d2da..8d5286376d 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -213,6 +213,8 @@ int MWWorld::ContainerStore::remove(const std::string& itemId, int count, const if (Misc::StringUtils::ciEqual(iter->getCellRef().mRefID, itemId)) toRemove -= remove(*iter, toRemove, actor); + flagAsModified(); + // number of removed items return count - toRemove; } @@ -235,6 +237,8 @@ int MWWorld::ContainerStore::remove(const Ptr& item, int count, const Ptr& actor toRemove = 0; } + flagAsModified(); + // number of removed items return count - toRemove; } From 60bec0398729f38a7e46f491e86e6c545ce17626 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 12 Nov 2013 23:23:19 +0100 Subject: [PATCH 242/434] Rename variables called 'slots' to work around wrong code parsing in QT Creator. --- apps/openmw/mwclass/armor.cpp | 12 +++++------ apps/openmw/mwclass/clothing.cpp | 16 +++++++-------- apps/openmw/mwclass/light.cpp | 6 +++--- apps/openmw/mwclass/lockpick.cpp | 6 +++--- apps/openmw/mwclass/probe.cpp | 6 +++--- apps/openmw/mwclass/weapon.cpp | 16 +++++++-------- apps/openmw/mwgui/spellwindow.cpp | 6 +++--- apps/openmw/mwworld/actionequip.cpp | 8 ++++---- apps/openmw/mwworld/inventorystore.cpp | 28 +++++++++++++------------- apps/openmw/mwworld/inventorystore.hpp | 2 +- 10 files changed, 53 insertions(+), 53 deletions(-) diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index a511207c41..c8e09d4333 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -94,7 +94,7 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - std::vector slots; + std::vector slots_; const int size = 11; @@ -116,11 +116,11 @@ namespace MWClass for (int i=0; imBase->mData.mType) { - slots.push_back (int (sMapping[i][1])); + slots_.push_back (int (sMapping[i][1])); break; } - return std::make_pair (slots, false); + return std::make_pair (slots_, false); } int Armor::getEquipmentSkill (const MWWorld::Ptr& ptr) const @@ -288,12 +288,12 @@ namespace MWClass MWWorld::InventoryStore& invStore = MWWorld::Class::get(npc).getInventoryStore(npc); // slots that this item can be equipped in - std::pair, bool> slots = MWWorld::Class::get(ptr).getEquipmentSlots(ptr); + std::pair, bool> slots_ = MWWorld::Class::get(ptr).getEquipmentSlots(ptr); std::string npcRace = npc.get()->mBase->mRace; - for (std::vector::const_iterator slot=slots.first.begin(); - slot!=slots.first.end(); ++slot) + for (std::vector::const_iterator slot=slots_.first.begin(); + slot!=slots_.first.end(); ++slot) { // Beast races cannot equip shoes / boots, or full helms (head part vs hair part) diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index 2dbb7ee6fd..0a23821a92 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -78,12 +78,12 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - std::vector slots; + std::vector slots_; if (ref->mBase->mData.mType==ESM::Clothing::Ring) { - slots.push_back (int (MWWorld::InventoryStore::Slot_LeftRing)); - slots.push_back (int (MWWorld::InventoryStore::Slot_RightRing)); + slots_.push_back (int (MWWorld::InventoryStore::Slot_LeftRing)); + slots_.push_back (int (MWWorld::InventoryStore::Slot_RightRing)); } else { @@ -105,12 +105,12 @@ namespace MWClass for (int i=0; imBase->mData.mType) { - slots.push_back (int (sMapping[i][1])); + slots_.push_back (int (sMapping[i][1])); break; } } - return std::make_pair (slots, false); + return std::make_pair (slots_, false); } int Clothing::getEquipmentSkill (const MWWorld::Ptr& ptr) const @@ -232,12 +232,12 @@ namespace MWClass std::pair Clothing::canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const { // slots that this item can be equipped in - std::pair, bool> slots = MWWorld::Class::get(ptr).getEquipmentSlots(ptr); + std::pair, bool> slots_ = MWWorld::Class::get(ptr).getEquipmentSlots(ptr); std::string npcRace = npc.get()->mBase->mRace; - for (std::vector::const_iterator slot=slots.first.begin(); - slot!=slots.first.end(); ++slot) + for (std::vector::const_iterator slot=slots_.first.begin(); + slot!=slots_.first.end(); ++slot) { // Beast races cannot equip shoes / boots, or full helms (head part vs hair part) diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index a593eb295a..a031d25563 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -121,12 +121,12 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - std::vector slots; + std::vector slots_; if (ref->mBase->mData.mFlags & ESM::Light::Carry) - slots.push_back (int (MWWorld::InventoryStore::Slot_CarriedLeft)); + slots_.push_back (int (MWWorld::InventoryStore::Slot_CarriedLeft)); - return std::make_pair (slots, false); + return std::make_pair (slots_, false); } int Light::getValue (const MWWorld::Ptr& ptr) const diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index 5931a0102d..aff36ba81d 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -74,11 +74,11 @@ namespace MWClass std::pair, bool> Lockpick::getEquipmentSlots (const MWWorld::Ptr& ptr) const { - std::vector slots; + std::vector slots_; - slots.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight)); + slots_.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight)); - return std::make_pair (slots, false); + return std::make_pair (slots_, false); } int Lockpick::getValue (const MWWorld::Ptr& ptr) const diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index 951265f40e..5cff140a65 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -73,11 +73,11 @@ namespace MWClass std::pair, bool> Probe::getEquipmentSlots (const MWWorld::Ptr& ptr) const { - std::vector slots; + std::vector slots_; - slots.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight)); + slots_.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight)); - return std::make_pair (slots, false); + return std::make_pair (slots_, false); } int Probe::getValue (const MWWorld::Ptr& ptr) const diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 4cb090328e..eaed597fcf 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -100,23 +100,23 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - std::vector slots; + std::vector slots_; bool stack = false; if (ref->mBase->mData.mType==ESM::Weapon::Arrow || ref->mBase->mData.mType==ESM::Weapon::Bolt) { - slots.push_back (int (MWWorld::InventoryStore::Slot_Ammunition)); + slots_.push_back (int (MWWorld::InventoryStore::Slot_Ammunition)); stack = true; } else if (ref->mBase->mData.mType==ESM::Weapon::MarksmanThrown) { - slots.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight)); + slots_.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight)); stack = true; } else - slots.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight)); + slots_.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight)); - return std::make_pair (slots, stack); + return std::make_pair (slots_, stack); } int Weapon::getEquipmentSkill (const MWWorld::Ptr& ptr) const @@ -384,11 +384,11 @@ namespace MWClass std::pair Weapon::canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const { - std::pair, bool> slots = MWWorld::Class::get(ptr).getEquipmentSlots(ptr); + std::pair, bool> slots_ = MWWorld::Class::get(ptr).getEquipmentSlots(ptr); // equip the item in the first free slot - for (std::vector::const_iterator slot=slots.first.begin(); - slot!=slots.first.end(); ++slot) + for (std::vector::const_iterator slot=slots_.first.begin(); + slot!=slots_.first.end(); ++slot) { if(*slot == MWWorld::InventoryStore::Slot_CarriedRight) { diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp index d5e3abc110..e9b2c0fa8a 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -110,9 +110,9 @@ namespace MWGui allowSelectedItem = false; // if the selected item can be equipped, make sure that it actually is equipped - std::pair, bool> slots; - slots = MWWorld::Class::get(selectedItem).getEquipmentSlots(selectedItem); - if (!slots.first.empty()) + std::pair, bool> slots_; + slots_ = MWWorld::Class::get(selectedItem).getEquipmentSlots(selectedItem); + if (!slots_.first.empty()) { bool equipped = false; for (int i=0; i < MWWorld::InventoryStore::Slots; ++i) diff --git a/apps/openmw/mwworld/actionequip.cpp b/apps/openmw/mwworld/actionequip.cpp index 38033340c0..2a50b8a600 100644 --- a/apps/openmw/mwworld/actionequip.cpp +++ b/apps/openmw/mwworld/actionequip.cpp @@ -40,7 +40,7 @@ namespace MWWorld } // slots that this item can be equipped in - std::pair, bool> slots = MWWorld::Class::get(getTarget()).getEquipmentSlots(getTarget()); + std::pair, bool> slots_ = MWWorld::Class::get(getTarget()).getEquipmentSlots(getTarget()); // retrieve ContainerStoreIterator to the item MWWorld::ContainerStoreIterator it = invStore.begin(); @@ -57,12 +57,12 @@ namespace MWWorld bool equipped = false; // equip the item in the first free slot - for (std::vector::const_iterator slot=slots.first.begin(); - slot!=slots.first.end(); ++slot) + for (std::vector::const_iterator slot=slots_.first.begin(); + slot!=slots_.first.end(); ++slot) { // if all slots are occupied, replace the last slot - if (slot == --slots.first.end()) + if (slot == --slots_.first.end()) { invStore.equip(*slot, it, actor); equipped = true; diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 7bfc11d846..a5711acfca 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -33,10 +33,10 @@ void MWWorld::InventoryStore::copySlots (const InventoryStore& store) } } -void MWWorld::InventoryStore::initSlots (TSlots& slots) +void MWWorld::InventoryStore::initSlots (TSlots& slots_) { for (int i=0; i, bool> slots; + std::pair, bool> slots_; if (iterator!=end()) { - slots = Class::get (*iterator).getEquipmentSlots (*iterator); + slots_ = Class::get (*iterator).getEquipmentSlots (*iterator); - if (std::find (slots.first.begin(), slots.first.end(), slot)==slots.first.end()) + if (std::find (slots_.first.begin(), slots_.first.end(), slot)==slots_.first.end()) throw std::runtime_error ("invalid slot"); } @@ -103,7 +103,7 @@ void MWWorld::InventoryStore::equip (int slot, const ContainerStoreIterator& ite unequipSlot(slot, actor); // unstack item pointed to by iterator if required - if (iterator!=end() && !slots.second && iterator->getRefData().getCount() > 1) // if slots.second is true, item can stay stacked when equipped + if (iterator!=end() && !slots_.second && iterator->getRefData().getCount() > 1) // if slots.second is true, item can stay stacked when equipped { // add the item again with a count of count-1, then set the count of the original (that will be equipped) to 1 int count = iterator->getRefData().getCount(); @@ -148,8 +148,8 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& npc) const MWMechanics::NpcStats& stats = MWWorld::Class::get(npc).getNpcStats(npc); MWWorld::InventoryStore& invStore = MWWorld::Class::get(npc).getInventoryStore(npc); - TSlots slots; - initSlots (slots); + TSlots slots_; + initSlots (slots_); // Disable model update during auto-equip mActorModelUpdateEnabled = false; @@ -167,11 +167,11 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& npc) { bool use = false; - if (slots.at (*iter2)==end()) + if (slots_.at (*iter2)==end()) use = true; // slot was empty before -> skip all further checks else { - Ptr old = *slots.at (*iter2); + Ptr old = *slots_.at (*iter2); if (!use) { @@ -229,15 +229,15 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& npc) } } - slots[*iter2] = iter; + slots_[*iter2] = iter; break; } } bool changed = false; - for (std::size_t i=0; i Date: Wed, 13 Nov 2013 00:00:01 +0100 Subject: [PATCH 243/434] Update actor model only when in the current cell --- apps/openmw/mwgui/inventorywindow.cpp | 2 ++ apps/openmw/mwworld/worldimp.cpp | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 0ae633aa05..da7ce6557c 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -253,6 +253,8 @@ namespace MWGui void InventoryWindow::open() { + mPtr = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + updateEncumbranceBar(); mItemView->update(); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index be71bebeee..30173a2d67 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2138,6 +2138,7 @@ namespace MWWorld void World::updateAnimParts(const Ptr& actor) { - mRendering->updateAnimParts(actor); + if (actor.mCell && actor.mCell == mWorldScene->getCurrentCell()) + mRendering->updateAnimParts(actor); } } From 0627e23d3ceebf8232853b4bd1801c9a6a718458 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 13 Nov 2013 01:26:25 +0100 Subject: [PATCH 244/434] Fix wrong skill level for spell success formula. --- apps/openmw/mwmechanics/spellsuccess.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/spellsuccess.hpp b/apps/openmw/mwmechanics/spellsuccess.hpp index 57c600df50..2cb12ffca4 100644 --- a/apps/openmw/mwmechanics/spellsuccess.hpp +++ b/apps/openmw/mwmechanics/spellsuccess.hpp @@ -88,7 +88,7 @@ namespace MWMechanics NpcStats& stats = MWWorld::Class::get(actor).getNpcStats(actor); CreatureStats& creatureStats = MWWorld::Class::get(actor).getCreatureStats(actor); - int skillLevel = stats.getSkill (getSpellSchool(spell, actor)).getModified(); + int skillLevel = stats.getSkill (spellSchoolToSkill(getSpellSchool(spell, actor))).getModified(); // Sound magic effect (reduces spell casting chance) int soundMagnitude = creatureStats.getMagicEffects().get (MWMechanics::EffectKey (48)).mMagnitude; From 821d0c5c2d7e48b72259eefcebdb5da4712c35fa Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Wed, 13 Nov 2013 11:38:29 +0100 Subject: [PATCH 245/434] Corrected filter template loading. --- apps/opencs/model/doc/document.cpp | 32 +++++++++++++++++------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index 590a19439c..4ee3c461fe 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -2245,29 +2245,33 @@ CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, co mData.setDescription (""); mData.setAuthor (""); } -/// \todo un-outcomment the else, once loading an existing content file works properly again. + if (boost::filesystem::exists (mProjectPath)) + { + std::cout<<"Loading file."< Date: Wed, 13 Nov 2013 14:02:15 +0100 Subject: [PATCH 246/434] Projectile models are now spawned (no movement or impact yet). Refactored trap activation to apply range types properly. Handle ContinuousVFX for magic effects (note they aren't stopped yet when the effect ends) --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwbase/world.hpp | 3 + apps/openmw/mwclass/container.cpp | 8 +-- apps/openmw/mwclass/door.cpp | 8 +-- apps/openmw/mwclass/npc.cpp | 5 +- apps/openmw/mwmechanics/activespells.cpp | 6 +- apps/openmw/mwmechanics/character.cpp | 2 +- apps/openmw/mwmechanics/magiceffects.hpp | 2 + apps/openmw/mwrender/animation.cpp | 69 +++++++++++++++++------ apps/openmw/mwrender/animation.hpp | 22 +++++++- apps/openmw/mwworld/actionapply.hpp | 1 - apps/openmw/mwworld/actiontrap.cpp | 28 +++++++++ apps/openmw/mwworld/actiontrap.hpp | 29 ++++++++++ apps/openmw/mwworld/inventorystore.cpp | 1 + apps/openmw/mwworld/worldimp.cpp | 72 ++++++++++++++++++++++++ apps/openmw/mwworld/worldimp.hpp | 19 +++++++ 16 files changed, 242 insertions(+), 35 deletions(-) create mode 100644 apps/openmw/mwworld/actiontrap.cpp create mode 100644 apps/openmw/mwworld/actiontrap.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 42fd11a713..04bd89f959 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -58,7 +58,7 @@ add_openmw_dir (mwworld cells localscripts customdata weather inventorystore ptr actionopen actionread actionequip timestamp actionalchemy cellstore actionapply actioneat esmstore store recordcmp fallback actionrepair actionsoulgem livecellref actiondoor - contentloader esmloader omwloader + contentloader esmloader omwloader actiontrap ) add_openmw_dir (mwclass diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index ddf8417448..ad3130ac5d 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -415,6 +415,9 @@ namespace MWBase virtual void castSpell (const MWWorld::Ptr& actor) = 0; virtual void updateAnimParts(const MWWorld::Ptr& ptr) = 0; + + virtual void launchProjectile (const std::string& id, const ESM::EffectList& effects, + const MWWorld::Ptr& actor, const std::string& sourceName) = 0; }; } diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 783eabff68..55b15a7d29 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -13,8 +13,8 @@ #include "../mwworld/containerstore.hpp" #include "../mwworld/customdata.hpp" #include "../mwworld/cellstore.hpp" -#include "../mwworld/actionapply.hpp" #include "../mwworld/actionopen.hpp" +#include "../mwworld/actiontrap.hpp" #include "../mwworld/physicssystem.hpp" #include "../mwworld/player.hpp" #include "../mwworld/inventorystore.hpp" @@ -147,11 +147,9 @@ namespace MWClass } else { - // Trap activation goes here - std::cout << "Activated trap: " << ptr.getCellRef().mTrap << std::endl; - boost::shared_ptr action(new MWWorld::ActionApply(actor, ptr.getCellRef().mTrap)); + // Activate trap + boost::shared_ptr action(new MWWorld::ActionTrap(actor, ptr.getCellRef().mTrap, ptr)); action->setSound(trapActivationSound); - ptr.getCellRef().mTrap = ""; return action; } } diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 3a0e4d0bab..3adb4f6fc3 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -12,12 +12,12 @@ #include "../mwworld/ptr.hpp" #include "../mwworld/nullaction.hpp" #include "../mwworld/failedaction.hpp" -#include "../mwworld/actionapply.hpp" #include "../mwworld/actionteleport.hpp" #include "../mwworld/actiondoor.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/physicssystem.hpp" #include "../mwworld/inventorystore.hpp" +#include "../mwworld/actiontrap.hpp" #include "../mwgui/tooltips.hpp" @@ -109,12 +109,8 @@ namespace MWClass if(!ptr.getCellRef().mTrap.empty()) { // Trap activation - std::cout << "Activated trap: " << ptr.getCellRef().mTrap << std::endl; - - boost::shared_ptr action(new MWWorld::ActionApply(actor, ptr.getCellRef().mTrap)); + boost::shared_ptr action(new MWWorld::ActionTrap(actor, ptr.getCellRef().mTrap, ptr)); action->setSound(trapActivationSound); - ptr.getCellRef().mTrap = ""; - return action; } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 492157993d..7862995801 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -467,9 +467,12 @@ namespace MWClass else { weapon.getCellRef().mEnchantmentCharge -= castCost; + // Touch othercls.getCreatureStats(victim).getActiveSpells().addSpell(enchantmentName, victim, ESM::RT_Touch, weapon.getClass().getName(weapon)); + // Self getCreatureStats(ptr).getActiveSpells().addSpell(enchantmentName, ptr, ESM::RT_Self, weapon.getClass().getName(weapon)); - // TODO: RT_Target + // Target + MWBase::Environment::get().getWorld()->launchProjectile(enchantmentName, enchantment->mEffects, ptr, weapon.getClass().getName(weapon)); } } } diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index 9e0ddb92b9..3bf10e08fc 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -39,6 +39,7 @@ namespace MWMechanics if (!timeToExpire (iter)) { mSpells.erase (iter++); + //onSpellExpired rebuild = true; } else @@ -227,6 +228,8 @@ namespace MWMechanics if (iter->mRange != range) continue; + // TODO: For Area effects, launch a growing particle effect that applies the effect to more actors as it hits them. Best managed in World. + const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find ( iter->mEffectID); @@ -248,7 +251,8 @@ namespace MWMechanics if (!magicEffect->mHit.empty()) { const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get().find (magicEffect->mHit); - MWBase::Environment::get().getWorld()->getAnimation(actor)->addEffect("meshes\\" + castStatic->mModel, ""); + bool loop = magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx; + MWBase::Environment::get().getWorld()->getAnimation(actor)->addEffect("meshes\\" + castStatic->mModel, loop, ""); } first = false; diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index fe1254b43a..929e012370 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -513,7 +513,7 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun effect = store.get().find(effectentry.mEffectID); const ESM::Static* castStatic = store.get().find (effect->mCasting); - mAnimation->addEffect("meshes\\" + castStatic->mModel, ""); + mAnimation->addEffect("meshes\\" + castStatic->mModel); switch(effectentry.mRange) { diff --git a/apps/openmw/mwmechanics/magiceffects.hpp b/apps/openmw/mwmechanics/magiceffects.hpp index b80b5a863d..d2559d3010 100644 --- a/apps/openmw/mwmechanics/magiceffects.hpp +++ b/apps/openmw/mwmechanics/magiceffects.hpp @@ -16,6 +16,8 @@ namespace MWMechanics int mId; int mArg; // skill or ability + // TODO: Add caster here for Absorb effects? + EffectKey(); EffectKey (int id, int arg = -1) : mId (id), mArg (arg) {} diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 6ef51945eb..3435df3939 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -87,8 +87,8 @@ Animation::Animation(const MWWorld::Ptr &ptr, Ogre::SceneNode *node) Animation::~Animation() { - for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ++it) - destroyObjectList(mInsert->getCreator(), *it); + for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ++it) + destroyObjectList(mInsert->getCreator(), it->mObjects); mAnimSources.clear(); @@ -929,22 +929,35 @@ Ogre::Vector3 Animation::runAnimation(float duration) } - for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ) + for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ) { - for(size_t i = 0; i < it->mControllers.size() ;i++) + NifOgre::ObjectList& objects = it->mObjects; + for(size_t i = 0; i < objects.mControllers.size() ;i++) { - static_cast (it->mControllers[i].getSource().get())->addTime(duration); + static_cast (objects.mControllers[i].getSource().get())->addTime(duration); - it->mControllers[i].update(); + objects.mControllers[i].update(); } - if (it->mControllers[0].getSource()->getValue() >= it->mMaxControllerLength) + if (objects.mControllers[0].getSource()->getValue() >= objects.mMaxControllerLength) { - destroyObjectList(mInsert->getCreator(), *it); - it = mEffects.erase(it); + if (it->mLoop) + { + // Start from the beginning again; carry over the remainder + float remainder = objects.mControllers[0].getSource()->getValue() - objects.mMaxControllerLength; + for(size_t i = 0; i < objects.mControllers.size() ;i++) + { + static_cast (objects.mControllers[i].getSource().get())->resetTime(remainder); + } + } + else + { + destroyObjectList(mInsert->getCreator(), it->mObjects); + it = mEffects.erase(it); + continue; + } } - else - ++it; + ++it; } return movement; @@ -1011,15 +1024,37 @@ void Animation::detachObjectFromBone(Ogre::MovableObject *obj) mSkelBase->detachObjectFromBone(obj); } -void Animation::addEffect(const std::string &model, const std::string &bonename) +void Animation::addEffect(const std::string &model, bool loop, const std::string &bonename) { - NifOgre::ObjectList list = NifOgre::Loader::createObjects(mInsert, model); - for(size_t i = 0;i < list.mControllers.size();i++) + // Early out if we already have this effect + for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ++it) + if (it->mModelName == model) + return; + + EffectParams params; + params.mModelName = model; + params.mObjects = NifOgre::Loader::createObjects(mInsert, model); + params.mLoop = loop; + + for(size_t i = 0;i < params.mObjects.mControllers.size();i++) { - if(list.mControllers[i].getSource().isNull()) - list.mControllers[i].setSource(Ogre::SharedPtr (new EffectAnimationValue())); + if(params.mObjects.mControllers[i].getSource().isNull()) + params.mObjects.mControllers[i].setSource(Ogre::SharedPtr (new EffectAnimationValue())); + } + mEffects.push_back(params); +} + +void Animation::removeEffect(const std::string &model) +{ + for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ++it) + { + if (it->mModelName == model) + { + destroyObjectList(mInsert->getCreator(), it->mObjects); + mEffects.erase(it); + return; + } } - mEffects.push_back(list); } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 6653b342ff..b983a59441 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -59,6 +59,7 @@ protected: public: EffectAnimationValue() : mTime(0) { } void addTime(float time) { mTime += time; } + void resetTime(float value) { mTime = value; } virtual Ogre::Real getValue() const; virtual void setValue(Ogre::Real value); @@ -108,7 +109,14 @@ protected: typedef std::map ObjectAttachMap; - std::vector mEffects; + struct EffectParams + { + std::string mModelName; // Just here so we don't add the same effect twice + NifOgre::ObjectList mObjects; + bool mLoop; + }; + + std::vector mEffects; MWWorld::Ptr mPtr; Camera *mCamera; @@ -189,7 +197,17 @@ public: Animation(const MWWorld::Ptr &ptr, Ogre::SceneNode *node); virtual ~Animation(); - void addEffect (const std::string& model, const std::string& bonename = ""); + /** + * @brief Add an effect mesh attached to a bone or the insert scene node + * @param model + * @param loop Loop the effect. If false, it is removed automatically after it finishes playing. If true, + * you need to remove it manually using removeEffect when the effect should end. + * @param bonename Bone to attach to, or empty string to use the scene node instead + * @note Will not add an effect twice. + */ + void addEffect (const std::string& model, bool loop = false, const std::string& bonename = ""); + + void removeEffect (const std::string& model); void updatePtr(const MWWorld::Ptr &ptr); diff --git a/apps/openmw/mwworld/actionapply.hpp b/apps/openmw/mwworld/actionapply.hpp index 3353ae0eed..669a190675 100644 --- a/apps/openmw/mwworld/actionapply.hpp +++ b/apps/openmw/mwworld/actionapply.hpp @@ -1,4 +1,3 @@ - #ifndef GAME_MWWORLD_ACTIONAPPLY_H #define GAME_MWWORLD_ACTIONAPPLY_H diff --git a/apps/openmw/mwworld/actiontrap.cpp b/apps/openmw/mwworld/actiontrap.cpp new file mode 100644 index 0000000000..13b2fd2694 --- /dev/null +++ b/apps/openmw/mwworld/actiontrap.cpp @@ -0,0 +1,28 @@ +#include "actiontrap.hpp" + +#include "../mwworld/class.hpp" + +#include "../mwmechanics/activespells.hpp" +#include "../mwmechanics/creaturestats.hpp" + +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" + +namespace MWWorld +{ + + void ActionTrap::executeImp(const Ptr &actor) + { + // TODO: Apply RT_Self effects on the door / container that triggered the trap. Not terribly useful, but you could + // make it lock itself when activated for example. + + actor.getClass().getCreatureStats(actor).getActiveSpells().addSpell(mSpellId, actor, ESM::RT_Touch); + + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(mSpellId); + + MWBase::Environment::get().getWorld()->launchProjectile(mSpellId, spell->mEffects, mTrapSource, spell->mName); + + mTrapSource.getCellRef().mTrap = ""; + } + +} diff --git a/apps/openmw/mwworld/actiontrap.hpp b/apps/openmw/mwworld/actiontrap.hpp new file mode 100644 index 0000000000..4c2f4139f7 --- /dev/null +++ b/apps/openmw/mwworld/actiontrap.hpp @@ -0,0 +1,29 @@ +#ifndef GAME_MWWORLD_ACTIONTRAP_H +#define GAME_MWWORLD_ACTIONTRAP_H + +#include + +#include "action.hpp" +#include "ptr.hpp" + +namespace MWWorld +{ + class ActionTrap : public Action + { + std::string mSpellId; + MWWorld::Ptr mTrapSource; + + virtual void executeImp (const Ptr& actor); + + public: + + /// @param spellId + /// @param actor Actor that activated the trap + /// @param trapSource + ActionTrap (const Ptr& actor, const std::string& spellId, const Ptr& trapSource) + : Action(false, actor), mSpellId(spellId), mTrapSource(trapSource) {} + }; +} + + +#endif diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index a5711acfca..c847a6574e 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -254,6 +254,7 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& npc) const MWMechanics::MagicEffects& MWWorld::InventoryStore::getMagicEffects() { + // TODO: Add VFX; update when needed instead of update on request if (!mMagicEffectsUpToDate) { mMagicEffects = MWMechanics::MagicEffects(); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index e343546c20..dd29c33a94 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1122,6 +1122,8 @@ namespace MWWorld processDoors(duration); + moveProjectiles(duration); + const PtrVelocityList &results = mPhysics->applyQueuedMovement(duration); PtrVelocityList::const_iterator player(results.end()); for(PtrVelocityList::const_iterator iter(results.begin());iter != results.end();iter++) @@ -2163,8 +2165,78 @@ namespace MWWorld } } + launchProjectile(selectedSpell, effects, actor, sourceName); + } + void World::launchProjectile (const std::string& id, const ESM::EffectList& effects, + const MWWorld::Ptr& actor, const std::string& sourceName) + { + std::string projectileModel; + std::string sound; + for (std::vector::const_iterator iter (effects.mList.begin()); + iter!=effects.mList.end(); ++iter) + { + if (iter->mRange != ESM::RT_Target) + continue; + + const ESM::MagicEffect *magicEffect = getStore().get().find ( + iter->mEffectID); + + projectileModel = magicEffect->mBolt; + + static const std::string schools[] = { + "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" + }; + + if (!magicEffect->mBoltSound.empty()) + sound = magicEffect->mBoltSound; + else + sound = schools[magicEffect->mData.mSchool] + " bolt"; + + break; + } + if (projectileModel.empty()) + return; + + MWWorld::ManualRef ref(getStore(), projectileModel); + ESM::Position pos; + pos.pos[0] = actor.getRefData().getPosition().pos[0]; + pos.pos[1] = actor.getRefData().getPosition().pos[1]; + pos.pos[2] = actor.getRefData().getPosition().pos[2]; + pos.rot[0] = actor.getRefData().getPosition().rot[0]; + pos.rot[1] = actor.getRefData().getPosition().rot[1]; + pos.rot[2] = actor.getRefData().getPosition().rot[2]; + ref.getPtr().getCellRef().mPos = pos; + MWWorld::Ptr ptr = copyObjectToCell(ref.getPtr(), *actor.getCell(), pos); + + ProjectileState state; + state.mSourceName = sourceName; + state.mId = id; + state.mActorHandle = actor.getRefData().getHandle(); + + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + sndMgr->playSound3D(ptr, sound, 1.0f, 1.0f); + + mProjectiles[ptr] = state; + } + + void World::moveProjectiles(float duration) + { + for (std::map::iterator it = mProjectiles.begin(); it != mProjectiles.end();) + { + if (!mWorldScene->isCellActive(*it->first.getCell())) + { + mProjectiles.erase(it++); + continue; + } + // TODO: Move + //moveObject(it->first, newPos.x, newPos.y, newPos.z); + ++it; + } + } + + void World::updateAnimParts(const Ptr& actor) { mRendering->updateAnimParts(actor); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 602d8d5e74..456d1491f9 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -87,6 +87,20 @@ namespace MWWorld std::map mDoorStates; ///< only holds doors that are currently moving. 0 means closing, 1 opening + struct ProjectileState + { + // Id of spell or enchantment to apply when it hits + std::string mId; + + // Actor who casted this projectile + std::string mActorHandle; + + // Name of item to display as effect source in magic menu (in case we casted an enchantment) + std::string mSourceName; + }; + + std::map mProjectiles; + int getDaysPerMonth (int month) const; void rotateObjectImp (const Ptr& ptr, Ogre::Vector3 rot, bool adjust); @@ -112,6 +126,8 @@ namespace MWWorld void processDoors(float duration); ///< Run physics simulation and modify \a world accordingly. + void moveProjectiles(float duration); + void doPhysics(float duration); ///< Run physics simulation and modify \a world accordingly. @@ -475,6 +491,9 @@ namespace MWWorld virtual void castSpell (const MWWorld::Ptr& actor); virtual void updateAnimParts(const MWWorld::Ptr& ptr); + + virtual void launchProjectile (const std::string& id, const ESM::EffectList& effects, + const MWWorld::Ptr& actor, const std::string& sourceName); }; } From 68598f9b634876c834e9be6090a2392901d7513f Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Wed, 13 Nov 2013 14:56:04 +0100 Subject: [PATCH 247/434] fixed handling of user directory defaultfilters file; some cleanup --- apps/opencs/model/doc/document.cpp | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index 4ee3c461fe..27f4f498a4 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -2245,33 +2245,39 @@ CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, co mData.setDescription (""); mData.setAuthor (""); } + + bool filtersFound = false; + if (boost::filesystem::exists (mProjectPath)) { - std::cout<<"Loading file."< Date: Wed, 13 Nov 2013 15:44:43 +0100 Subject: [PATCH 248/434] Stop continuous VFX when the effect is no longer active. --- apps/openmw/mwgui/spellicons.cpp | 2 + apps/openmw/mwmechanics/activespells.cpp | 2 +- apps/openmw/mwmechanics/actors.cpp | 1 - apps/openmw/mwmechanics/character.cpp | 20 +++++- apps/openmw/mwmechanics/character.hpp | 2 + apps/openmw/mwmechanics/spells.hpp | 2 + apps/openmw/mwrender/animation.cpp | 82 ++++++++++++++---------- apps/openmw/mwrender/animation.hpp | 10 ++- components/esm/loadmgef.hpp | 4 +- 9 files changed, 85 insertions(+), 40 deletions(-) diff --git a/apps/openmw/mwgui/spellicons.cpp b/apps/openmw/mwgui/spellicons.cpp index aa2474fa52..da51f7e8f1 100644 --- a/apps/openmw/mwgui/spellicons.cpp +++ b/apps/openmw/mwgui/spellicons.cpp @@ -23,6 +23,8 @@ namespace MWGui void SpellIcons::updateWidgets(MyGUI::Widget *parent, bool adjustSize) { + // TODO: Tracking add/remove/expire would be better than force updating every frame + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); const MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index 3bf10e08fc..88fa57a8f6 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -252,7 +252,7 @@ namespace MWMechanics { const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get().find (magicEffect->mHit); bool loop = magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx; - MWBase::Environment::get().getWorld()->getAnimation(actor)->addEffect("meshes\\" + castStatic->mModel, loop, ""); + MWBase::Environment::get().getWorld()->getAnimation(actor)->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, ""); } first = false; diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 4ed7f71b4a..6cb0dc55a3 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -378,7 +378,6 @@ namespace MWMechanics iter->second->update(duration); } } - void Actors::restoreDynamicStats() { for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 929e012370..0a13f7c8f3 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -513,7 +513,7 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun effect = store.get().find(effectentry.mEffectID); const ESM::Static* castStatic = store.get().find (effect->mCasting); - mAnimation->addEffect("meshes\\" + castStatic->mModel); + mAnimation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex); switch(effectentry.mRange) { @@ -716,6 +716,8 @@ void CharacterController::update(float duration) const MWWorld::Class &cls = MWWorld::Class::get(mPtr); Ogre::Vector3 movement(0.0f); + updateContinuousVfx(); + if(!cls.isActor()) { if(mAnimQueue.size() > 1) @@ -1103,4 +1105,20 @@ void CharacterController::resurrect() mDeathState = CharState_None; } +void CharacterController::updateContinuousVfx() +{ + // Keeping track of when to stop a continuous VFX seems to be very difficult to do inside the spells code, + // as it's extremely spread out (ActiveSpells, Spells, InventoryStore effects, etc...) so we do it here. + + // Stop any effects that are no longer active + std::vector effects; + mAnimation->getLoopingEffects(effects); + + for (std::vector::iterator it = effects.begin(); it != effects.end(); ++it) + { + if (mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(MWMechanics::EffectKey(*it)).mMagnitude <= 0) + mAnimation->removeEffect(*it); + } +} + } diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 8670b385e3..c82b29b479 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -173,6 +173,8 @@ class CharacterController bool updateNpcState(bool onground, bool inwater, bool isrunning, bool sneak); + void updateContinuousVfx(); + public: CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim); virtual ~CharacterController(); diff --git a/apps/openmw/mwmechanics/spells.hpp b/apps/openmw/mwmechanics/spells.hpp index e00ac259f1..8f6a750a46 100644 --- a/apps/openmw/mwmechanics/spells.hpp +++ b/apps/openmw/mwmechanics/spells.hpp @@ -4,6 +4,8 @@ #include #include +#include "../mwworld/ptr.hpp" + namespace ESM { struct Spell; diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 3435df3939..db2482c934 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -928,37 +928,7 @@ Ogre::Vector3 Animation::runAnimation(float duration) mSkelBase->getAllAnimationStates()->_notifyDirty(); } - - for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ) - { - NifOgre::ObjectList& objects = it->mObjects; - for(size_t i = 0; i < objects.mControllers.size() ;i++) - { - static_cast (objects.mControllers[i].getSource().get())->addTime(duration); - - objects.mControllers[i].update(); - } - - if (objects.mControllers[0].getSource()->getValue() >= objects.mMaxControllerLength) - { - if (it->mLoop) - { - // Start from the beginning again; carry over the remainder - float remainder = objects.mControllers[0].getSource()->getValue() - objects.mMaxControllerLength; - for(size_t i = 0; i < objects.mControllers.size() ;i++) - { - static_cast (objects.mControllers[i].getSource().get())->resetTime(remainder); - } - } - else - { - destroyObjectList(mInsert->getCreator(), it->mObjects); - it = mEffects.erase(it); - continue; - } - } - ++it; - } + updateEffects(duration); return movement; } @@ -1024,7 +994,7 @@ void Animation::detachObjectFromBone(Ogre::MovableObject *obj) mSkelBase->detachObjectFromBone(obj); } -void Animation::addEffect(const std::string &model, bool loop, const std::string &bonename) +void Animation::addEffect(const std::string &model, int effectId, bool loop, const std::string &bonename) { // Early out if we already have this effect for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ++it) @@ -1035,6 +1005,7 @@ void Animation::addEffect(const std::string &model, bool loop, const std::string params.mModelName = model; params.mObjects = NifOgre::Loader::createObjects(mInsert, model); params.mLoop = loop; + params.mEffectId = effectId; for(size_t i = 0;i < params.mObjects.mControllers.size();i++) { @@ -1044,11 +1015,11 @@ void Animation::addEffect(const std::string &model, bool loop, const std::string mEffects.push_back(params); } -void Animation::removeEffect(const std::string &model) +void Animation::removeEffect(int effectId) { for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ++it) { - if (it->mModelName == model) + if (it->mEffectId == effectId) { destroyObjectList(mInsert->getCreator(), it->mObjects); mEffects.erase(it); @@ -1057,6 +1028,49 @@ void Animation::removeEffect(const std::string &model) } } +void Animation::getLoopingEffects(std::vector &out) +{ + for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ++it) + { + if (it->mLoop) + out.push_back(it->mEffectId); + } +} + +void Animation::updateEffects(float duration) +{ + for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ) + { + NifOgre::ObjectList& objects = it->mObjects; + for(size_t i = 0; i < objects.mControllers.size() ;i++) + { + static_cast (objects.mControllers[i].getSource().get())->addTime(duration); + + objects.mControllers[i].update(); + } + + if (objects.mControllers[0].getSource()->getValue() >= objects.mMaxControllerLength) + { + if (it->mLoop) + { + // Start from the beginning again; carry over the remainder + float remainder = objects.mControllers[0].getSource()->getValue() - objects.mMaxControllerLength; + for(size_t i = 0; i < objects.mControllers.size() ;i++) + { + static_cast (objects.mControllers[i].getSource().get())->resetTime(remainder); + } + } + else + { + destroyObjectList(mInsert->getCreator(), it->mObjects); + it = mEffects.erase(it); + continue; + } + } + ++it; + } +} + ObjectAnimation::ObjectAnimation(const MWWorld::Ptr& ptr, const std::string &model) : Animation(ptr, ptr.getRefData().getBaseNode()) diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index b983a59441..11adf0c588 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -113,6 +113,7 @@ protected: { std::string mModelName; // Just here so we don't add the same effect twice NifOgre::ObjectList mObjects; + int mEffectId; bool mLoop; }; @@ -200,15 +201,20 @@ public: /** * @brief Add an effect mesh attached to a bone or the insert scene node * @param model + * @param effectId An ID for this effect. Note that adding the same ID again won't add another effect. * @param loop Loop the effect. If false, it is removed automatically after it finishes playing. If true, * you need to remove it manually using removeEffect when the effect should end. * @param bonename Bone to attach to, or empty string to use the scene node instead * @note Will not add an effect twice. */ - void addEffect (const std::string& model, bool loop = false, const std::string& bonename = ""); + void addEffect (const std::string& model, int effectId, bool loop = false, const std::string& bonename = ""); + void removeEffect (int effectId); + void getLoopingEffects (std::vector& out); +private: + void updateEffects(float duration); - void removeEffect (const std::string& model); +public: void updatePtr(const MWWorld::Ptr &ptr); bool hasAnimation(const std::string &anim); diff --git a/components/esm/loadmgef.hpp b/components/esm/loadmgef.hpp index d18507b100..b1047e94af 100644 --- a/components/esm/loadmgef.hpp +++ b/components/esm/loadmgef.hpp @@ -232,7 +232,9 @@ struct MagicEffect SummonBear = 139, SummonBonewolf = 140, SummonCreature04 = 141, - SummonCreature05 = 142 + SummonCreature05 = 142, + + Length }; }; } From ff7e4174f9a2d580d324da5a4b03d52205dc5691 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 13 Nov 2013 18:51:28 +0100 Subject: [PATCH 249/434] Fix some leftover code that still calculated random magnitude per spell rather than per effect. Major cleanup of InventoryStore: Magic effects are now updated when needed, rather than cached. Also allows to get rid of 'mutable' hacks and non-const method that should be const. Play sounds and particles when equipping a permanent enchantment item. --- apps/openmw/mwmechanics/magiceffects.cpp | 22 ---- apps/openmw/mwmechanics/magiceffects.hpp | 5 +- apps/openmw/mwmechanics/spells.cpp | 19 +++- apps/openmw/mwmechanics/spells.hpp | 2 +- apps/openmw/mwworld/containerstore.cpp | 6 -- apps/openmw/mwworld/containerstore.hpp | 5 - apps/openmw/mwworld/inventorystore.cpp | 131 ++++++++++++++++++----- apps/openmw/mwworld/inventorystore.hpp | 21 ++-- 8 files changed, 138 insertions(+), 73 deletions(-) diff --git a/apps/openmw/mwmechanics/magiceffects.cpp b/apps/openmw/mwmechanics/magiceffects.cpp index 3ed458c3fa..5be0854aba 100644 --- a/apps/openmw/mwmechanics/magiceffects.cpp +++ b/apps/openmw/mwmechanics/magiceffects.cpp @@ -68,28 +68,6 @@ namespace MWMechanics } } - void MagicEffects::add (const ESM::EffectList& list, float magnitude) - { - for (std::vector::const_iterator iter (list.mList.begin()); iter!=list.mList.end(); - ++iter) - { - EffectParam param; - - if (iter->mMagnMin>=iter->mMagnMax) - param.mMagnitude = iter->mMagnMin; - else - { - if (magnitude==-1) - magnitude = static_cast (std::rand()) / RAND_MAX; - - param.mMagnitude = static_cast ( - (iter->mMagnMax-iter->mMagnMin+1)*magnitude + iter->mMagnMin); - } - - add (*iter, param); - } - } - MagicEffects& MagicEffects::operator+= (const MagicEffects& effects) { if (this==&effects) diff --git a/apps/openmw/mwmechanics/magiceffects.hpp b/apps/openmw/mwmechanics/magiceffects.hpp index d2559d3010..212ef312dd 100644 --- a/apps/openmw/mwmechanics/magiceffects.hpp +++ b/apps/openmw/mwmechanics/magiceffects.hpp @@ -33,6 +33,8 @@ namespace MWMechanics EffectParam(); + EffectParam(int magnitude) : mMagnitude(magnitude) {} + EffectParam& operator+= (const EffectParam& param); EffectParam& operator-= (const EffectParam& param); @@ -69,9 +71,6 @@ namespace MWMechanics void add (const EffectKey& key, const EffectParam& param); - void add (const ESM::EffectList& list, float magnitude = -1); - ///< \param magnitude normalised magnitude (-1: random) - MagicEffects& operator+= (const MagicEffects& effects); EffectParam get (const EffectKey& key) const; diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index e10dcdc93d..caf3c64818 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -27,7 +27,15 @@ namespace MWMechanics void Spells::add (const std::string& spellId) { if (mSpells.find (spellId)==mSpells.end()) - mSpells.insert (std::make_pair (spellId, static_cast (std::rand()) / RAND_MAX)); + { + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(spellId); + + std::vector random; + random.resize(spell->mEffects.mList.size()); + for (int i=0; i (std::rand()) / RAND_MAX; + mSpells.insert (std::make_pair (spellId, random)); + } } void Spells::remove (const std::string& spellId) @@ -52,7 +60,14 @@ namespace MWMechanics if (spell->mData.mType==ESM::Spell::ST_Ability || spell->mData.mType==ESM::Spell::ST_Blight || spell->mData.mType==ESM::Spell::ST_Disease || spell->mData.mType==ESM::Spell::ST_Curse) - effects.add (spell->mEffects, iter->second); + { + int i=0; + for (std::vector::const_iterator it = spell->mEffects.mList.begin(); it != spell->mEffects.mList.end(); ++it) + { + effects.add (*it, iter->second[i]); + ++i; + } + } } return effects; diff --git a/apps/openmw/mwmechanics/spells.hpp b/apps/openmw/mwmechanics/spells.hpp index 8f6a750a46..ccac96190b 100644 --- a/apps/openmw/mwmechanics/spells.hpp +++ b/apps/openmw/mwmechanics/spells.hpp @@ -23,7 +23,7 @@ namespace MWMechanics { public: - typedef std::map TContainer; // ID, normalised magnitude + typedef std::map > TContainer; // ID, normalised magnitudes typedef TContainer::const_iterator TIterator; private: diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index 8d5286376d..2aee240894 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -333,15 +333,9 @@ void MWWorld::ContainerStore::clear() void MWWorld::ContainerStore::flagAsModified() { - ++mStateId; mWeightUpToDate = false; } -int MWWorld::ContainerStore::getStateId() const -{ - return mStateId; -} - float MWWorld::ContainerStore::getWeight() const { if (!mWeightUpToDate) diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index ca6609ecf2..701553c74d 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -104,11 +104,6 @@ namespace MWWorld ///< \attention This function is internal to the world model and should not be called from /// outside. - int getStateId() const; - ///< This ID is changed every time the container is modified or items in the container - /// are accessed in a way that may be used to modify the item. - /// \note This method of change-tracking will ocasionally yield false positives. - float getWeight() const; ///< Return total weight of the items contained in *this. diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index c847a6574e..7a5e59f36d 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -9,6 +9,9 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/soundmanager.hpp" + +#include "../mwrender/animation.hpp" #include "../mwmechanics/npcstats.hpp" @@ -39,9 +42,9 @@ void MWWorld::InventoryStore::initSlots (TSlots& slots_) slots_.push_back (end()); } -MWWorld::InventoryStore::InventoryStore() : mMagicEffectsUpToDate (false) - , mSelectedEnchantItem(end()) - , mActorModelUpdateEnabled (true) +MWWorld::InventoryStore::InventoryStore() + : mSelectedEnchantItem(end()) + , mUpdatesEnabled (true) { initSlots (mSlots); } @@ -51,15 +54,15 @@ MWWorld::InventoryStore::InventoryStore (const InventoryStore& store) , mSelectedEnchantItem(end()) { mMagicEffects = store.mMagicEffects; - mMagicEffectsUpToDate = store.mMagicEffectsUpToDate; mSelectedEnchantItem = store.mSelectedEnchantItem; + mPermanentMagicEffectMagnitudes = store.mPermanentMagicEffectMagnitudes; copySlots (store); } MWWorld::InventoryStore& MWWorld::InventoryStore::operator= (const InventoryStore& store) { mMagicEffects = store.mMagicEffects; - mMagicEffectsUpToDate = store.mMagicEffectsUpToDate; + mPermanentMagicEffectMagnitudes = store.mPermanentMagicEffectMagnitudes; ContainerStore::operator= (store); mSlots.clear(); copySlots (store); @@ -117,6 +120,8 @@ void MWWorld::InventoryStore::equip (int slot, const ContainerStoreIterator& ite flagAsModified(); updateActorModel(actor); + + updateMagicEffects(actor); } void MWWorld::InventoryStore::unequipAll(const MWWorld::Ptr& actor) @@ -135,9 +140,9 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getSlot (int slot) if (mSlots[slot]->getRefData().getCount()<1) { - // object has been deleted - mSlots[slot] = end(); - return end(); + // Object has been deleted + // This should no longer happen, since the new remove function will unequip first + throw std::runtime_error("Invalid slot, make sure you are not calling RefData::setCount for a container object"); } return mSlots[slot]; @@ -152,7 +157,7 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& npc) initSlots (slots_); // Disable model update during auto-equip - mActorModelUpdateEnabled = false; + mUpdatesEnabled = false; for (ContainerStoreIterator iter (begin()); iter!=end(); ++iter) { @@ -242,48 +247,119 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& npc) changed = true; } - mActorModelUpdateEnabled = true; + mUpdatesEnabled = true; if (changed) { mSlots.swap (slots_); updateActorModel(npc); + updateMagicEffects(npc); flagAsModified(); } } -const MWMechanics::MagicEffects& MWWorld::InventoryStore::getMagicEffects() +const MWMechanics::MagicEffects& MWWorld::InventoryStore::getMagicEffects() const { - // TODO: Add VFX; update when needed instead of update on request - if (!mMagicEffectsUpToDate) + return mMagicEffects; +} + +void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) +{ + // To avoid excessive updates during auto-equip + if (!mUpdatesEnabled) + return; + + mMagicEffects = MWMechanics::MagicEffects(); + + for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter) { - mMagicEffects = MWMechanics::MagicEffects(); + if (*iter==end()) + continue; - for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter) - if (*iter!=end()) + std::string enchantmentId = MWWorld::Class::get (**iter).getEnchantment (**iter); + + if (!enchantmentId.empty()) + { + const ESM::Enchantment& enchantment = + *MWBase::Environment::get().getWorld()->getStore().get().find (enchantmentId); + + if (enchantment.mData.mType != ESM::Enchantment::ConstantEffect) + continue; + + // Roll some dice, one for each effect + std::vector random; + random.resize(enchantment.mEffects.mList.size()); + for (unsigned int i=0; i (std::rand()) / RAND_MAX; + + int i=0; + for (std::vector::const_iterator effectIt (enchantment.mEffects.mList.begin()); + effectIt!=enchantment.mEffects.mList.end(); ++effectIt) { - std::string enchantmentId = MWWorld::Class::get (**iter).getEnchantment (**iter); + const ESM::MagicEffect *magicEffect = + MWBase::Environment::get().getWorld()->getStore().get().find ( + effectIt->mEffectID); - if (!enchantmentId.empty()) + if (mPermanentMagicEffectMagnitudes.find((**iter).getCellRef().mRefID) == mPermanentMagicEffectMagnitudes.end()) { - const ESM::Enchantment& enchantment = - *MWBase::Environment::get().getWorld()->getStore().get().find (enchantmentId); + // Note that using the RefID as a key here is not entirely correct. + // Consider equipping the same item twice (e.g. a ring) + // However, permanent enchantments with a random magnitude are kind of an exploit anyway, + // so it doesn't really matter if both items will get the same magnitude. *Extreme* edge case. + mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID] = random; - if (enchantment.mData.mType==ESM::Enchantment::ConstantEffect) - mMagicEffects.add (enchantment.mEffects); + // Only the sound of the first effect plays + if (effectIt == enchantment.mEffects.mList.begin()) + { + static const std::string schools[] = { + "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" + }; + + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + if(!magicEffect->mHitSound.empty()) + sndMgr->playSound3D(actor, magicEffect->mHitSound, 1.0f, 1.0f); + else + sndMgr->playSound3D(actor, schools[magicEffect->mData.mSchool]+" hit", 1.0f, 1.0f); + } + + if (!magicEffect->mHit.empty()) + { + const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get().find (magicEffect->mHit); + bool loop = magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx; + MWBase::Environment::get().getWorld()->getAnimation(actor)->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, ""); + } } - } - mMagicEffectsUpToDate = true; + mMagicEffects.add (*effectIt, random[i]); + ++i; + } + } } - return mMagicEffects; + // Now drop expired effects + for (TEffectMagnitudes::iterator it = mPermanentMagicEffectMagnitudes.begin(); + it != mPermanentMagicEffectMagnitudes.end();) + { + bool found = false; + for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter) + { + if (*iter == end()) + continue; + if ((**iter).getCellRef().mRefID == it->first) + { + found = true; + } + } + if (!found) + mPermanentMagicEffectMagnitudes.erase(it++); + else + ++it; + } } void MWWorld::InventoryStore::flagAsModified() { ContainerStore::flagAsModified(); - mMagicEffectsUpToDate = false; } bool MWWorld::InventoryStore::stacks(const Ptr& ptr1, const Ptr& ptr2) @@ -394,6 +470,7 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, c } updateActorModel(actor); + updateMagicEffects(actor); return retval; } @@ -415,6 +492,6 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipItem(const MWWor void MWWorld::InventoryStore::updateActorModel(const MWWorld::Ptr& actor) { - if (mActorModelUpdateEnabled) + if (mUpdatesEnabled) MWBase::Environment::get().getWorld()->updateAnimParts(actor); } diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index e3d53b5e11..bccdcb991c 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -43,13 +43,20 @@ namespace MWWorld private: - mutable MWMechanics::MagicEffects mMagicEffects; - mutable bool mMagicEffectsUpToDate; - bool mActorModelUpdateEnabled; + MWMechanics::MagicEffects mMagicEffects; + + // Enables updates of magic effects and actor model whenever items are equipped or unequipped. + // This is disabled during autoequip to avoid excessive updates + bool mUpdatesEnabled; + + // Vanilla allows permanent effects with a random magnitude, so it needs to be stored here. + // We also need this to only play sounds and particle effects when the item is equipped, rather than on every update. + typedef std::map > TEffectMagnitudes; + TEffectMagnitudes mPermanentMagicEffectMagnitudes; typedef std::vector TSlots; - mutable TSlots mSlots; + TSlots mSlots; // selected magic item (for using enchantments of type "Cast once" or "Cast when used") ContainerStoreIterator mSelectedEnchantItem; @@ -60,6 +67,8 @@ namespace MWWorld void updateActorModel (const Ptr& actor); + void updateMagicEffects(const Ptr& actor); + public: InventoryStore(); @@ -98,10 +107,8 @@ namespace MWWorld void autoEquip (const MWWorld::Ptr& npc); ///< Auto equip items according to stats and item value. - const MWMechanics::MagicEffects& getMagicEffects(); + const MWMechanics::MagicEffects& getMagicEffects() const; ///< Return magic effects from worn items. - /// - /// \todo make this const again, after the constness of Ptrs and iterators has been addressed. virtual void flagAsModified(); ///< \attention This function is internal to the world model and should not be called from From da5c59c8afbdebdc3fe23fa82862c89f3a543bae Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 13 Nov 2013 19:07:44 +0100 Subject: [PATCH 250/434] addEffect should check effectId, not model. --- apps/openmw/mwrender/animation.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index db2482c934..d92912fa95 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -998,7 +998,7 @@ void Animation::addEffect(const std::string &model, int effectId, bool loop, con { // Early out if we already have this effect for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ++it) - if (it->mModelName == model) + if (it->mEffectId == effectId) return; EffectParams params; From 1eaca1e26b4933ebb8ad3a29d69879059a8a6f7f Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Wed, 13 Nov 2013 21:01:48 +0100 Subject: [PATCH 251/434] Added regular expressions tutorial. Because serpentine failed me :( --- manual/opencs/filters.tex | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/manual/opencs/filters.tex b/manual/opencs/filters.tex index 2c5e0939e8..2a3d1a4800 100644 --- a/manual/opencs/filters.tex +++ b/manual/opencs/filters.tex @@ -70,8 +70,15 @@ This is probably enough to create around 90\% string filters you will eventually \\ Creating regexps can be a difficult and annoying -- especially when you need complex criteria. On the other hand, We are under impression that in reality complex expressions are needed only in sporadic cases. In fact, the truth is: that most of the time only already mentioned ``.*'' is needed and therefore the following description of regexps can be skipped by vast majority of readers.\\ -%TO-DO: write the regexps essentials. -Regular expressions are not the main topic of this manual. If you wish to learn more on this subject please, read the documentation on Qt regular expressions syntax, or TRE regexp syntax (it is almost like in Qt). +Before working with Regular Expressions, you should understand what actually are regular expressions. Essentially, the idea is simple: when you are writing any word, you are using strictly defined latters -- that is: latters create a word. What you want to do with regular expression is to use set of rules that will match to many words. It is not that difficult to see what it's needed to do so: first, you will clearly need way to determinate what latters you want to match (word is composed by latters).\\ + +Before introducing other ways to choose between characters, I want explain anchors. Anchors allows you to decide where to ``look'' in the string. You surely should know about ``^'' anchor and ``\$''. Putting ``^`` will tell to Open{CS} to look on the beginning of string, while ''\$`` is used to mark the end of it. For instance, pattern ''^Pink.* elephant.\$`` Will match any sentence Beginning with the word ''Pink`` and ending with '' elephant.``. Pink fat elephant. Pink cute elephant. It doe not matter what is in between because ''.*`` is used.\\ + +You have already seen the power of the simple ``.*''. But what if you want to chose between only two (or more) latters? Well, this is when ``[|]'' comes in handy. If you write something like: ``^[a|k].*'' you are simply telling Open{CS} to filter anything that starts with either ``a'' or ``k''. Using ``^[a|k|l].*'' will work in the same manner, but it will also cover strings starting with ``l''.\\ + +And What if you want to match more than just one latter, just use ``(|)`` it is pretty similar to the above, but it is used to fit more than just one character. For instance: ''^(Pink|Green).* (elephant|crocodile).\$`` will be true for all sentences starting with ''Pink`` or ''Green`` and ending with either ''elephant.`` or ''crocodile.``.\\ + +Regular expressions are not the main topic of this manual. If you wish to learn more on this subject please, read the documentation on Qt regular expressions syntax, or TRE regexp syntax (it is almost like in Qt). Above is just enough to use Open{CS} effectively to be sure. \paragraph{Value -- value(``value'', (``open'', ``close''))} While string expression covers vast group of columns containing string values, there are in fact columns with just numerical values like ``weight``. To filter those we need a value expression. This one works in similar manner to the string filter: first token name and criteria inside brackets. Clearly, conditions should hold column to test in. However in this case wanted value is specified as a range.\\ From 078745c5d3a8f445127ec3dbc50020c3598251b0 Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Wed, 13 Nov 2013 21:28:09 +0100 Subject: [PATCH 252/434] Added some shiny PR to the windows, honestly. It is just at the begining of the manual, so maybe it will raise morale of the user ;-) --- manual/opencs/filters.tex | 6 +++--- manual/opencs/windows.tex | 17 ++++++++++++++++- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/manual/opencs/filters.tex b/manual/opencs/filters.tex index 2a3d1a4800..93c29c439a 100644 --- a/manual/opencs/filters.tex +++ b/manual/opencs/filters.tex @@ -70,15 +70,15 @@ This is probably enough to create around 90\% string filters you will eventually \\ Creating regexps can be a difficult and annoying -- especially when you need complex criteria. On the other hand, We are under impression that in reality complex expressions are needed only in sporadic cases. In fact, the truth is: that most of the time only already mentioned ``.*'' is needed and therefore the following description of regexps can be skipped by vast majority of readers.\\ -Before working with Regular Expressions, you should understand what actually are regular expressions. Essentially, the idea is simple: when you are writing any word, you are using strictly defined latters -- that is: latters create a word. What you want to do with regular expression is to use set of rules that will match to many words. It is not that difficult to see what it's needed to do so: first, you will clearly need way to determinate what latters you want to match (word is composed by latters).\\ +Before working with Regular Expressions, you should understand what actually are regular expressions. Essentially, the idea is simple: when you are writing any word, you are using strictly defined letters -- that is: letters create a word. What you want to do with regular expression is to use set of rules that will match to many words. It is not that difficult to see what it's needed to do so: first, you will clearly need way to determinate what letters you want to match (word is composed by letters).\\ Before introducing other ways to choose between characters, I want explain anchors. Anchors allows you to decide where to ``look'' in the string. You surely should know about ``^'' anchor and ``\$''. Putting ``^`` will tell to Open{CS} to look on the beginning of string, while ''\$`` is used to mark the end of it. For instance, pattern ''^Pink.* elephant.\$`` Will match any sentence Beginning with the word ''Pink`` and ending with '' elephant.``. Pink fat elephant. Pink cute elephant. It doe not matter what is in between because ''.*`` is used.\\ -You have already seen the power of the simple ``.*''. But what if you want to chose between only two (or more) latters? Well, this is when ``[|]'' comes in handy. If you write something like: ``^[a|k].*'' you are simply telling Open{CS} to filter anything that starts with either ``a'' or ``k''. Using ``^[a|k|l].*'' will work in the same manner, but it will also cover strings starting with ``l''.\\ +You have already seen the power of the simple ``.*''. But what if you want to chose between only two (or more) letters? Well, this is when ``[|]'' comes in handy. If you write something like: ``^[a|k].*'' you are simply telling Open{CS} to filter anything that starts with either ``a'' or ``k''. Using ``^[a|k|l].*'' will work in the same manner, but it will also cover strings starting with ``l''.\\ And What if you want to match more than just one latter, just use ``(|)`` it is pretty similar to the above, but it is used to fit more than just one character. For instance: ''^(Pink|Green).* (elephant|crocodile).\$`` will be true for all sentences starting with ''Pink`` or ''Green`` and ending with either ''elephant.`` or ''crocodile.``.\\ -Regular expressions are not the main topic of this manual. If you wish to learn more on this subject please, read the documentation on Qt regular expressions syntax, or TRE regexp syntax (it is almost like in Qt). Above is just enough to use Open{CS} effectively to be sure. +Regular expressions are not the main topic of this manual. If you wish to learn more on this subject please, read the documentation on Qt regular expressions syntax, or TRE regexp syntax (it is almost like in Qt). Above is just enough to use Open{CS} effectively to be sure.\\ \paragraph{Value -- value(``value'', (``open'', ``close''))} While string expression covers vast group of columns containing string values, there are in fact columns with just numerical values like ``weight``. To filter those we need a value expression. This one works in similar manner to the string filter: first token name and criteria inside brackets. Clearly, conditions should hold column to test in. However in this case wanted value is specified as a range.\\ diff --git a/manual/opencs/windows.tex b/manual/opencs/windows.tex index ad1d2f951c..3efe77f494 100644 --- a/manual/opencs/windows.tex +++ b/manual/opencs/windows.tex @@ -9,4 +9,19 @@ After starting Open{CS} and choosing content files to use a editor window should You probably founded out the way to enable and disable some interesting tables, but those will be described later. For now, let's just focus on the windows itself. \paragraph{Creating new windows} -is easy! Just visit view menu, and use the ``New View'' item. Suddenly, out of the blue a new window will show up. As you would expect, it is also blank, and you are free to add any of the Open{CS} widgets. \ No newline at end of file +is easy! Just visit view menu, and use the ``New View'' item. Suddenly, out of the blue a new window will show up. As you would expect, it is also blank, and you are free to add any of the Open{CS} widgets. + +\paragraph{Closing opened window} +is also easy! Simply close that window decoration button. We suspect that you knew that already, but better to be sure. Closing last Open{CS} window will also terminate application session. + +\paragraph{Multi-everything} +is the main foundation of Open{CS} interface. You are free to create as many windows as you want to, free to populate it with any widgets you may want to, and move everything as you wish to -- even if it makes no sense at all. If you just got crazy idea and you are wonder if you are able to have one hundred Open{CS} windows showing widget of the same type, well most likely you are able to do so.\\ + +The principle behind this design decision is easy to see for Bethesda made editor, but maybe not so clear for users who are just about to begin their wonderful journey of modding.\\ + +\subsection{Advanced} +So why? Why this is created in such manner. The answer is frankly simple: because it is effective. When creating a mod, you often have to work only with just one table. For instance you are just balancing weapons damage and other statistics. It makes sense to have all the space for just that one table. More often, you are required to work with two and switch them from time to time. All major graphical environments commonly present in operating systems comes with switcher feature, that is a key shortcut to change active window. It is very effective and fast when you have only two windows, each holding only one table. Sometimes you have to work with two at the time, and with one from time to time. Here, you can have one window holding two table widgets, and second holding just one.\\ + +Open{CS} is designed to simply make sense and do not slowdown users. It is as simple as possible (but not simpler), and uses one flexible approach in all cases.\\ + +There is no point in digging deeper in the windows of Open{CS}. Let's explore widgets, starting with tables. \ No newline at end of file From 6197ebd35f8e46fb51b7a699216bdb45e90f9852 Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Wed, 13 Nov 2013 21:37:27 +0100 Subject: [PATCH 253/434] Actually builds now. --- manual/opencs/filters.tex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/manual/opencs/filters.tex b/manual/opencs/filters.tex index 93c29c439a..f13f51c8a1 100644 --- a/manual/opencs/filters.tex +++ b/manual/opencs/filters.tex @@ -72,11 +72,11 @@ Creating regexps can be a difficult and annoying -- especially when you need com Before working with Regular Expressions, you should understand what actually are regular expressions. Essentially, the idea is simple: when you are writing any word, you are using strictly defined letters -- that is: letters create a word. What you want to do with regular expression is to use set of rules that will match to many words. It is not that difficult to see what it's needed to do so: first, you will clearly need way to determinate what letters you want to match (word is composed by letters).\\ -Before introducing other ways to choose between characters, I want explain anchors. Anchors allows you to decide where to ``look'' in the string. You surely should know about ``^'' anchor and ``\$''. Putting ``^`` will tell to Open{CS} to look on the beginning of string, while ''\$`` is used to mark the end of it. For instance, pattern ''^Pink.* elephant.\$`` Will match any sentence Beginning with the word ''Pink`` and ending with '' elephant.``. Pink fat elephant. Pink cute elephant. It doe not matter what is in between because ''.*`` is used.\\ +Before introducing other ways to choose between characters, I want explain anchors. Anchors allows you to decide where to ``look'' in the string. You surely should know about ``\^'' anchor and ``\textdollar''. Putting ``\^`` will tell to Open{CS} to look on the beginning of string, while ''\textdollar`` is used to mark the end of it. For instance, pattern ''\^Pink.* elephant.\textdollar`` Will match any sentence Beginning with the word ''Pink`` and ending with '' elephant.``. Pink fat elephant. Pink cute elephant. It doe not matter what is in between because ''.*`` is used.\\ -You have already seen the power of the simple ``.*''. But what if you want to chose between only two (or more) letters? Well, this is when ``[|]'' comes in handy. If you write something like: ``^[a|k].*'' you are simply telling Open{CS} to filter anything that starts with either ``a'' or ``k''. Using ``^[a|k|l].*'' will work in the same manner, but it will also cover strings starting with ``l''.\\ +You have already seen the power of the simple ``.*''. But what if you want to chose between only two (or more) letters? Well, this is when ``[|]'' comes in handy. If you write something like: ``\^[a|k].*'' you are simply telling Open{CS} to filter anything that starts with either ``a'' or ``k''. Using ``\^[a|k|l].*'' will work in the same manner, but it will also cover strings starting with ``l''.\\ -And What if you want to match more than just one latter, just use ``(|)`` it is pretty similar to the above, but it is used to fit more than just one character. For instance: ''^(Pink|Green).* (elephant|crocodile).\$`` will be true for all sentences starting with ''Pink`` or ''Green`` and ending with either ''elephant.`` or ''crocodile.``.\\ +And What if you want to match more than just one latter, just use ``(|)`` it is pretty similar to the above, but it is used to fit more than just one character. For instance: ''\^(Pink|Green).* (elephant|crocodile).\textdollar`` will be true for all sentences starting with ''Pink`` or ''Green`` and ending with either ''elephant.`` or ''crocodile.``.\\ Regular expressions are not the main topic of this manual. If you wish to learn more on this subject please, read the documentation on Qt regular expressions syntax, or TRE regexp syntax (it is almost like in Qt). Above is just enough to use Open{CS} effectively to be sure.\\ From 28a98df3aa2390fedcb1d9a6027a9c6b7ec49942 Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Wed, 13 Nov 2013 21:49:29 +0100 Subject: [PATCH 254/434] Some corrections. --- manual/opencs/filters.tex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manual/opencs/filters.tex b/manual/opencs/filters.tex index f13f51c8a1..0499fb7f92 100644 --- a/manual/opencs/filters.tex +++ b/manual/opencs/filters.tex @@ -1,4 +1,4 @@ -\section{Filters} +\section{Record filters} \subsection{Introduction} Filters are the key element of OpenCS use cases by allowing rapid and easy access to the searched records presented in all tables. Therefore: in order to use this application fully effective you should make sure that all concepts and instructions written in the this section of the manual are perfectly clear to you.\\ Don't be afraid though, filters are fairly intuitive and easy to use. @@ -10,7 +10,7 @@ Don't be afraid though, filters are fairly intuitive and easy to use. \item[Criteria] describes condition under with any any record is being select by the filter. \item[Syntax] as you may noticed computers (in general) are rather strict, and expect only strictly formulated orders -- that is: written with correct syntax. \item[Expression] is way we are actually performing filtering. Filter can be treated as ``functions'': accepts arguments, and evaluates either to the true or false for every column record at the time. - \item[N-ary] is any expression that expects two or more expressions as arguments. It is useful for grouping two (or more) other expressions together in order to create filter that will check for criteria placed in two (again: or more) columns (logical ``or'', ``and''). + \item[N-ary] is any expression that expects one or more expressions as arguments. It is useful for grouping two (or more) other expressions together in order to create filter that will check for criteria placed in two (again: or more) columns (logical ``or'', ``and''). \item[unary] is any expression that expects one other expression. The example is ``not'' expression. In fact ``not'' is the only useful unary expression in OpenCS record filters. \item[nullary] is expression that does not accepts other expressions. It accepts arguments specified later. \end{description} From 17d41ff032d1f3a70048cead94ef3a3a7699211c Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Thu, 14 Nov 2013 08:22:52 +0100 Subject: [PATCH 255/434] Corrected according to the zini suggestions. --- manual/opencs/filters.tex | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/manual/opencs/filters.tex b/manual/opencs/filters.tex index 0499fb7f92..82834e02cb 100644 --- a/manual/opencs/filters.tex +++ b/manual/opencs/filters.tex @@ -44,14 +44,14 @@ We strongly recommend to take a look at the filters table right now to see what \subsection{Advanced} Back to the manual? Great.\\ If you want to create your own filter you have to know exactly what do you want to get in order to translate this into the expressions. Finally, you will have to write this with correct syntax. As a result table will show only desired rows.\\ -Advance subsection covers everything that you need to know in order to create any filter you may want to. +Advance subsection covers everything that you need to know in order to create any filter you may want to %TODO the filter part is actually wrong \subsubsection{Namespaces} -Did you noticed that every default filter has ``project::`` prefix? It is a ``namespace``, a term borrowed from the C++ language. In case of OpenCS namespace of the filter determinate if the filter will be stored along with your project file or if it will be forgotten as soon as OpenCS quits. +Did you noticed that every default filter has ``project::`` prefix? It is a ``namespace``, a term borrowed from the C++ language. In case of OpenCS namespace always means scope of the said object\footnote{You are not supposed to understand this at the moment.}. But what does it mean in case of filters? Well, short explanation is actually simple. \begin{description} - \item[project::] namespace indicates that filter is stored inside the project file. - \item[session::] namespace indicates that filter is not stored inside the project file, and once you will quit OpenCS (close session) the filter will be gone. Forever! Until then it can be found inside the filters table. + \item[project::] namespace indicates that filter is used with the project, in multiple sessions. You can restart Open{CS} and filter is still there. + \item[session::] namespace indicates that filter is not stored trough multiple sessions and once you will quit Open{CS} (close session) the filter will be gone. Forever! Until then it can be found inside the filters table. \end{description} -In addition to this two scopes, there is a third one; called one-shot. One-shot filters are not stored anywhere and as the name implies they are supposed to be created when needed only once. Good thing about the one-shot filters is that you don't need to open filters table in order to create it. Instead you just type it directly inside the filter field, starting with ``!''.\\ +In addition to this two scopes, there is a third one; called one-shot. One-shot filters are not stored (even during single session) anywhere and as the name implies they are supposed to be created when needed only once. Good thing about the one-shot filters is that you don't need to open filters table in order to create it. Instead you just type it directly inside the filter field, starting with ``!''.\\ Still, you may wonder how you are supposed to write expressions, what expressions you should use, and what syntax looks like. Let's start with nullary expressions that will allow you to create a basic filter. \subsubsection{Nullary expressions} @@ -89,6 +89,9 @@ As you would imagine the range can be specified as including a border value, or \item Mixing brackets is completely legal. For value equal 10, expression value(something, [5, 10) will evaluate to true. The same expression will evaluate to false for value equal 10. \end{itemize} +\paragraph{''true`` and ''false``} +Nullary ''true`` and ''false`` do not accept any arguments, and always evaluates to true (in case of ''true``) and false (in case of ''false``) no matter what. The main usage of this expressions is the give users ability to quickly disable some part of the filter that makes heavy use of the logical expressions. + \subsubsection{Logical expressions} This subsection takes care of two remaining groups of expressions: binary and unary. The only unary expression present in the OpenCS is logical not, while the remaining binary expressions are: or, and. This clearly makes them (from the user point of view) belonging to the same group of logical expressions. From 99f72d4b612e40d5eea539b1fc3a8c94ec5cfd3d Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Thu, 14 Nov 2013 08:35:03 +0100 Subject: [PATCH 256/434] =?UTF-8?q?Filters:=20typos.=20Windows:=20widgets?= =?UTF-8?q?=E2=86=92name=20panels.=20I=20have=20no=20idea=20why=20we=20are?= =?UTF-8?q?=20inventin=20our=20own=20terms=20here.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- manual/opencs/filters.tex | 4 ++-- manual/opencs/windows.tex | 12 +++++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/manual/opencs/filters.tex b/manual/opencs/filters.tex index 82834e02cb..cf0fc72026 100644 --- a/manual/opencs/filters.tex +++ b/manual/opencs/filters.tex @@ -72,11 +72,11 @@ Creating regexps can be a difficult and annoying -- especially when you need com Before working with Regular Expressions, you should understand what actually are regular expressions. Essentially, the idea is simple: when you are writing any word, you are using strictly defined letters -- that is: letters create a word. What you want to do with regular expression is to use set of rules that will match to many words. It is not that difficult to see what it's needed to do so: first, you will clearly need way to determinate what letters you want to match (word is composed by letters).\\ -Before introducing other ways to choose between characters, I want explain anchors. Anchors allows you to decide where to ``look'' in the string. You surely should know about ``\^'' anchor and ``\textdollar''. Putting ``\^`` will tell to Open{CS} to look on the beginning of string, while ''\textdollar`` is used to mark the end of it. For instance, pattern ''\^Pink.* elephant.\textdollar`` Will match any sentence Beginning with the word ''Pink`` and ending with '' elephant.``. Pink fat elephant. Pink cute elephant. It doe not matter what is in between because ''.*`` is used.\\ +Before introducing other ways to choose between characters, I want explain anchors. Anchors allows you to decide where to ``look'' in the string. You surely should know about ``\^'' anchor and ``\textdollar''. Putting ``\^`` will tell to Open{CS} to look on the beginning of string, while ''\textdollar`` is used to mark the end of it. For instance, pattern ''\^Pink.* elephant.\textdollar`` Will match any sentence Beginning with the word ''Pink`` and ending with '' elephant.``. Pink fat elephant. Pink cute elephant. It does not matter what is in between, because ''.*`` is used.\\ You have already seen the power of the simple ``.*''. But what if you want to chose between only two (or more) letters? Well, this is when ``[|]'' comes in handy. If you write something like: ``\^[a|k].*'' you are simply telling Open{CS} to filter anything that starts with either ``a'' or ``k''. Using ``\^[a|k|l].*'' will work in the same manner, but it will also cover strings starting with ``l''.\\ -And What if you want to match more than just one latter, just use ``(|)`` it is pretty similar to the above, but it is used to fit more than just one character. For instance: ''\^(Pink|Green).* (elephant|crocodile).\textdollar`` will be true for all sentences starting with ''Pink`` or ''Green`` and ending with either ''elephant.`` or ''crocodile.``.\\ +And What if you want to match more than just one latter? Just use ``(|)``. it is pretty similar to the above one letter as you see, but it is used to fit more than just one character. For instance: ''\^(Pink|Green).* (elephant|crocodile).\textdollar`` will be true for all sentences starting with ''Pink`` or ''Green`` and ending with either ''elephant.`` or ''crocodile.``.\\ Regular expressions are not the main topic of this manual. If you wish to learn more on this subject please, read the documentation on Qt regular expressions syntax, or TRE regexp syntax (it is almost like in Qt). Above is just enough to use Open{CS} effectively to be sure.\\ diff --git a/manual/opencs/windows.tex b/manual/opencs/windows.tex index 3efe77f494..b5b34e7881 100644 --- a/manual/opencs/windows.tex +++ b/manual/opencs/windows.tex @@ -5,23 +5,25 @@ OpenCS windows interface is easy to describe and understand. In fact We decided Because of this, and the fact that we expect that user is familiar with other applications using windows this section is mostly focused on practical ways of organizing work with the OpenCS. \subsection{Basics} -After starting Open{CS} and choosing content files to use a editor window should show up. It probably does not look surprising: there is a menubar at the top, and there is a large empty area. That's it: a brand new Open{CS} window contains only menubar and statusbar. In order to make it a little bit more useful you probably want to enable some window widgets. You are free to do so, just try to explore the menubar. \\ +After starting Open{CS} and choosing content files to use a editor window should show up. It probably does not look surprising: there is a menubar at the top, and there is a large empty area. That's it: a brand new Open{CS} window contains only menubar and statusbar. In order to make it a little bit more useful you probably want to enable some name panels\footnote{Also known as widgets.}. You are free to do so, just try to explore the menubar. \\ You probably founded out the way to enable and disable some interesting tables, but those will be described later. For now, let's just focus on the windows itself. \paragraph{Creating new windows} -is easy! Just visit view menu, and use the ``New View'' item. Suddenly, out of the blue a new window will show up. As you would expect, it is also blank, and you are free to add any of the Open{CS} widgets. +is easy! Just visit view menu, and use the ``New View'' item. Suddenly, out of the blue a new window will show up. As you would expect, it is also blank, and you are free to add any of the Open{CS} name panels. \paragraph{Closing opened window} is also easy! Simply close that window decoration button. We suspect that you knew that already, but better to be sure. Closing last Open{CS} window will also terminate application session. \paragraph{Multi-everything} -is the main foundation of Open{CS} interface. You are free to create as many windows as you want to, free to populate it with any widgets you may want to, and move everything as you wish to -- even if it makes no sense at all. If you just got crazy idea and you are wonder if you are able to have one hundred Open{CS} windows showing widget of the same type, well most likely you are able to do so.\\ +is the main foundation of Open{CS} interface. You are free to create as many windows as you want to, free to populate it with any name panels you may want to, and move everything as you wish to -- even if it makes no sense at all. If you just got crazy idea and you are wonder if you are able to have one hundred Open{CS} windows showing name panels of the same type, well most likely you are able to do so.\\ The principle behind this design decision is easy to see for Bethesda made editor, but maybe not so clear for users who are just about to begin their wonderful journey of modding.\\ \subsection{Advanced} -So why? Why this is created in such manner. The answer is frankly simple: because it is effective. When creating a mod, you often have to work only with just one table. For instance you are just balancing weapons damage and other statistics. It makes sense to have all the space for just that one table. More often, you are required to work with two and switch them from time to time. All major graphical environments commonly present in operating systems comes with switcher feature, that is a key shortcut to change active window. It is very effective and fast when you have only two windows, each holding only one table. Sometimes you have to work with two at the time, and with one from time to time. Here, you can have one window holding two table widgets, and second holding just one.\\ +So why? Why this is created in such manner. The answer is frankly simple: because it is effective. When creating a mod, you often have to work only with just one table. For instance you are just balancing weapons damage and other statistics. It makes sense to have all the space for just that one table. More often, you are required to work with two and switch them from time to time. All major graphical environments commonly present in operating systems comes with switcher feature, that is a key shortcut to change active window. It is very effective and fast when you have only two windows, each holding only one table. Sometimes you have to work with two at the time, and with one from time to time. Here, you can have one window holding two tables, and second holding just one.\\ Open{CS} is designed to simply make sense and do not slowdown users. It is as simple as possible (but not simpler), and uses one flexible approach in all cases.\\ -There is no point in digging deeper in the windows of Open{CS}. Let's explore widgets, starting with tables. \ No newline at end of file +There is no point in digging deeper in the windows of Open{CS}. Let's explore name panels, starting with tables. + +%We should write some tips and tricks here. \ No newline at end of file From 780ea3a41f25577e46e4f27a5c0b25cdd12192a2 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 14 Nov 2013 11:39:14 +0100 Subject: [PATCH 257/434] added support for record reordering to model (only implemented in info collection) --- apps/opencs/model/world/collection.hpp | 50 +++++++++++ apps/opencs/model/world/collectionbase.hpp | 7 ++ apps/opencs/model/world/commands.cpp | 22 +++++ apps/opencs/model/world/commands.hpp | 16 ++++ apps/opencs/model/world/idtable.cpp | 24 ++++-- apps/opencs/model/world/idtable.hpp | 21 ++++- apps/opencs/model/world/infocollection.cpp | 16 ++++ apps/opencs/model/world/infocollection.hpp | 6 ++ apps/opencs/model/world/refidcollection.cpp | 5 ++ apps/opencs/model/world/refidcollection.hpp | 6 ++ apps/opencs/view/world/table.cpp | 96 +++++++++++++++++++++ apps/opencs/view/world/table.hpp | 6 ++ 12 files changed, 267 insertions(+), 8 deletions(-) diff --git a/apps/opencs/model/world/collection.hpp b/apps/opencs/model/world/collection.hpp index 316e76c5f1..3f410e3971 100644 --- a/apps/opencs/model/world/collection.hpp +++ b/apps/opencs/model/world/collection.hpp @@ -57,6 +57,12 @@ namespace CSMWorld const std::vector >& getRecords() const; + bool reorderRowsImp (int baseIndex, const std::vector& newOrder); + ///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices + /// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex). + /// + /// \return Success? + public: Collection(); @@ -128,6 +134,12 @@ namespace CSMWorld /// If the index is invalid either generally (by being out of range) or for the particular /// record, an exception is thrown. + virtual bool reorderRows (int baseIndex, const std::vector& newOrder); + ///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices + /// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex). + /// + /// \return Success? + void addColumn (Column *column); void setRecord (int index, const Record& record); @@ -146,6 +158,38 @@ namespace CSMWorld return mRecords; } + template + bool Collection::reorderRowsImp (int baseIndex, + const std::vector& newOrder) + { + if (!newOrder.empty()) + { + int size = static_cast (newOrder.size()); + + // check that all indices are present + std::vector test (newOrder); + std::sort (test.begin(), test.end()); + if (*test.begin()!=0 || *--test.end()!=size-1) + return false; + + // reorder records + std::vector > buffer (size); + + for (int i=0; i::iterator iter (mIndex.begin()); iter!=mIndex.end(); + ++iter) + if (iter->second>=baseIndex && iter->secondsecond = newOrder.at (iter->second-baseIndex)+baseIndex; + } + + return true; + } + template Collection::Collection() {} @@ -389,6 +433,12 @@ namespace CSMWorld mRecords.at (index) = record; } + + template + bool Collection::reorderRows (int baseIndex, const std::vector& newOrder) + { + return false; + } } #endif diff --git a/apps/opencs/model/world/collectionbase.hpp b/apps/opencs/model/world/collectionbase.hpp index 2d1b5318c7..ab7a9ebecb 100644 --- a/apps/opencs/model/world/collectionbase.hpp +++ b/apps/opencs/model/world/collectionbase.hpp @@ -2,6 +2,7 @@ #define CSM_WOLRD_COLLECTIONBASE_H #include +#include #include "universalid.hpp" #include "columns.hpp" @@ -86,6 +87,12 @@ namespace CSMWorld /// /// \param listDeleted include deleted record in the list + virtual bool reorderRows (int baseIndex, const std::vector& newOrder) = 0; + ///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices + /// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex). + /// + /// \return Success? + int searchColumnIndex (Columns::ColumnId id) const; ///< Return index of column with the given \a id. If no such column exists, -1 is returned. diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp index f6f421c6ae..21feb14be6 100644 --- a/apps/opencs/model/world/commands.cpp +++ b/apps/opencs/model/world/commands.cpp @@ -122,4 +122,26 @@ void CSMWorld::DeleteCommand::redo() void CSMWorld::DeleteCommand::undo() { mModel.setRecord (mId, *mOld); +} + + +CSMWorld::ReorderRowsCommand::ReorderRowsCommand (IdTable& model, int baseIndex, + const std::vector& newOrder) +: mModel (model), mBaseIndex (baseIndex), mNewOrder (newOrder) +{} + +void CSMWorld::ReorderRowsCommand::redo() +{ + mModel.reorderRows (mBaseIndex, mNewOrder); +} + +void CSMWorld::ReorderRowsCommand::undo() +{ + int size = static_cast (mNewOrder.size()); + std::vector reverse (size); + + for (int i=0; i #include +#include #include #include @@ -99,6 +100,21 @@ namespace CSMWorld virtual void undo(); }; + + class ReorderRowsCommand : public QUndoCommand + { + IdTable& mModel; + int mBaseIndex; + std::vector mNewOrder; + + public: + + ReorderRowsCommand (IdTable& model, int baseIndex, const std::vector& newOrder); + + virtual void redo(); + + virtual void undo(); + }; } #endif \ No newline at end of file diff --git a/apps/opencs/model/world/idtable.cpp b/apps/opencs/model/world/idtable.cpp index f5cd14efc4..809d64339c 100644 --- a/apps/opencs/model/world/idtable.cpp +++ b/apps/opencs/model/world/idtable.cpp @@ -4,15 +4,12 @@ #include "collectionbase.hpp" #include "columnbase.hpp" -CSMWorld::IdTable::IdTable (CollectionBase *idCollection) : mIdCollection (idCollection) -{ - -} +CSMWorld::IdTable::IdTable (CollectionBase *idCollection, Reordering reordering) +: mIdCollection (idCollection), mReordering (reordering) +{} CSMWorld::IdTable::~IdTable() -{ - -} +{} int CSMWorld::IdTable::rowCount (const QModelIndex & parent) const { @@ -167,4 +164,17 @@ int CSMWorld::IdTable::searchColumnIndex (Columns::ColumnId id) const int CSMWorld::IdTable::findColumnIndex (Columns::ColumnId id) const { return mIdCollection->findColumnIndex (id); +} + +void CSMWorld::IdTable::reorderRows (int baseIndex, const std::vector& newOrder) +{ + if (!newOrder.empty()) + if (mIdCollection->reorderRows (baseIndex, newOrder)) + emit dataChanged (index (baseIndex, 0), + index (baseIndex+newOrder.size()-1, mIdCollection->getColumns()-1)); +} + +CSMWorld::IdTable::Reordering CSMWorld::IdTable::getReordering() const +{ + return mReordering; } \ No newline at end of file diff --git a/apps/opencs/model/world/idtable.hpp b/apps/opencs/model/world/idtable.hpp index 00c5772366..e4ae58fd04 100644 --- a/apps/opencs/model/world/idtable.hpp +++ b/apps/opencs/model/world/idtable.hpp @@ -1,6 +1,8 @@ #ifndef CSM_WOLRD_IDTABLE_H #define CSM_WOLRD_IDTABLE_H +#include + #include #include "universalid.hpp" @@ -15,7 +17,18 @@ namespace CSMWorld { Q_OBJECT + public: + + enum Reordering + { + Reordering_None, + Reordering_WithinTopic + }; + + private: + CollectionBase *mIdCollection; + Reordering mReordering; // not implemented IdTable (const IdTable&); @@ -23,7 +36,7 @@ namespace CSMWorld public: - IdTable (CollectionBase *idCollection); + IdTable (CollectionBase *idCollection, Reordering reordering = Reordering_WithinTopic); ///< The ownership of \a idCollection is not transferred. virtual ~IdTable(); @@ -63,6 +76,12 @@ namespace CSMWorld int findColumnIndex (Columns::ColumnId id) const; ///< Return index of column with the given \a id. If no such column exists, an exception is /// thrown. + + void reorderRows (int baseIndex, const std::vector& newOrder); + ///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices + /// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex). + + Reordering getReordering() const; }; } diff --git a/apps/opencs/model/world/infocollection.cpp b/apps/opencs/model/world/infocollection.cpp index b5c1872a11..87bb925c2e 100644 --- a/apps/opencs/model/world/infocollection.cpp +++ b/apps/opencs/model/world/infocollection.cpp @@ -88,6 +88,22 @@ int CSMWorld::InfoCollection::getAppendIndex (const std::string& id, UniversalId return std::distance (getRecords().begin(), range.second); } +bool CSMWorld::InfoCollection::reorderRows (int baseIndex, const std::vector& newOrder) +{ + // check if the range is valid + int lastIndex = baseIndex + newOrder.size() -1; + + if (lastIndex>=getSize()) + return false; + + // Check that topics match + if (getRecord (baseIndex).get().mTopicId!=getRecord (lastIndex).get().mTopicId) + return false; + + // reorder + return reorderRowsImp (baseIndex, newOrder); +} + void CSMWorld::InfoCollection::load (ESM::ESMReader& reader, bool base, const ESM::Dialogue& dialogue) { std::string id = Misc::StringUtils::lowerCase (dialogue.mId) + "#" + diff --git a/apps/opencs/model/world/infocollection.hpp b/apps/opencs/model/world/infocollection.hpp index 1bccbb4aee..ae61f5d391 100644 --- a/apps/opencs/model/world/infocollection.hpp +++ b/apps/opencs/model/world/infocollection.hpp @@ -33,6 +33,12 @@ namespace CSMWorld UniversalId::Type type = UniversalId::Type_None) const; ///< \param type Will be ignored, unless the collection supports multiple record types + virtual bool reorderRows (int baseIndex, const std::vector& newOrder); + ///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices + /// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex). + /// + /// \return Success? + void load (ESM::ESMReader& reader, bool base, const ESM::Dialogue& dialogue); Range getTopicRange (const std::string& topic) const; diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index 0218642e0e..86a542c5c3 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -540,6 +540,11 @@ std::vector CSMWorld::RefIdCollection::getIds (bool listDeleted) co return mData.getIds (listDeleted); } +bool CSMWorld::RefIdCollection::reorderRows (int baseIndex, const std::vector& newOrder) +{ + return false; +} + void CSMWorld::RefIdCollection::save (int index, ESM::ESMWriter& writer) const { mData.save (index, writer); diff --git a/apps/opencs/model/world/refidcollection.hpp b/apps/opencs/model/world/refidcollection.hpp index 0788b60237..5ff4a70bf2 100644 --- a/apps/opencs/model/world/refidcollection.hpp +++ b/apps/opencs/model/world/refidcollection.hpp @@ -100,6 +100,12 @@ namespace CSMWorld /// /// \param listDeleted include deleted record in the list + virtual bool reorderRows (int baseIndex, const std::vector& newOrder); + ///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices + /// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex). + /// + /// \return Success? + void save (int index, ESM::ESMWriter& writer) const; }; } diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index dfdfa6d13e..dade9c8ea3 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -12,6 +12,7 @@ #include "../../model/world/idtableproxymodel.hpp" #include "../../model/world/idtable.hpp" #include "../../model/world/record.hpp" +#include "../../model/world/columns.hpp" #include "recordstatusdelegate.hpp" #include "util.hpp" @@ -37,6 +38,35 @@ void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) if (listDeletableSelectedIds().size()>0) menu.addAction (mDeleteAction); + + if (mModel->getReordering()==CSMWorld::IdTable::Reordering_WithinTopic) + { + /// \todo allow reordering of multiple rows + if (selectedRows.size()==1) + { + int row =selectedRows.begin()->row(); + + int column = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Topic); + + if (column==-1) + column = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Journal); + + if (column!=-1) + { + if (row>0 && mProxyModel->data (mProxyModel->index (row, column))== + mProxyModel->data (mProxyModel->index (row-1, column))) + { + menu.addAction (mMoveUpAction); + } + + if (rowrowCount()-1 && mProxyModel->data (mProxyModel->index (row, column))== + mProxyModel->data (mProxyModel->index (row+1, column))) + { + menu.addAction (mMoveDownAction); + } + } + } + } } menu.exec (event->globalPos()); @@ -176,6 +206,14 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, Q connect (mDeleteAction, SIGNAL (triggered()), this, SLOT (deleteRecord())); addAction (mDeleteAction); + mMoveUpAction = new QAction (tr ("Move Up"), this); + connect (mMoveUpAction, SIGNAL (triggered()), this, SLOT (moveUpRecord())); + addAction (mMoveUpAction); + + mMoveDownAction = new QAction (tr ("Move Down"), this); + connect (mMoveDownAction, SIGNAL (triggered()), this, SLOT (moveDownRecord())); + addAction (mMoveDownAction); + connect (mProxyModel, SIGNAL (rowsInserted (const QModelIndex&, int, int)), this, SLOT (tableSizeUpdate())); @@ -254,6 +292,64 @@ void CSVWorld::Table::editRecord() } } +void CSVWorld::Table::moveUpRecord() +{ + QModelIndexList selectedRows = selectionModel()->selectedRows(); + + if (selectedRows.size()==1) + { + int row2 =selectedRows.begin()->row(); + + if (row2>0) + { + int row = row2-1; + + row = mProxyModel->mapToSource (mProxyModel->index (row, 0)).row(); + row2 = mProxyModel->mapToSource (mProxyModel->index (row2, 0)).row(); + + if (row2<=row) + throw std::runtime_error ("Inconsistent row order"); + + std::vector newOrder (row2-row+1); + newOrder[0] = row2-row; + newOrder[row2-row] = 0; + for (int i=1; iselectedRows(); + + if (selectedRows.size()==1) + { + int row =selectedRows.begin()->row(); + + if (rowrowCount()-1) + { + int row2 = row+1; + + row = mProxyModel->mapToSource (mProxyModel->index (row, 0)).row(); + row2 = mProxyModel->mapToSource (mProxyModel->index (row2, 0)).row(); + + if (row2<=row) + throw std::runtime_error ("Inconsistent row order"); + + std::vector newOrder (row2-row+1); + newOrder[0] = row2-row; + newOrder[row2-row] = 0; + for (int i=1; icolumnCount(); diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp index 3e42144248..889e2847ac 100644 --- a/apps/opencs/view/world/table.hpp +++ b/apps/opencs/view/world/table.hpp @@ -34,6 +34,8 @@ namespace CSVWorld QAction *mCreateAction; QAction *mRevertAction; QAction *mDeleteAction; + QAction *mMoveUpAction; + QAction *mMoveDownAction; CSMWorld::IdTableProxyModel *mProxyModel; CSMWorld::IdTable *mModel; bool mEditLock; @@ -80,6 +82,10 @@ namespace CSVWorld void editRecord(); + void moveUpRecord(); + + void moveDownRecord(); + public slots: void tableSizeUpdate(); From a44fad4c5987663175ddf0cef75ceaaa692911b8 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 14 Nov 2013 11:55:02 +0100 Subject: [PATCH 258/434] disabled revert for info tables (no easy way to make this work) --- apps/opencs/view/world/table.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index dade9c8ea3..d4ccba4a7b 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -33,8 +33,11 @@ void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) if (mCreateAction) menu.addAction (mCreateAction); - if (listRevertableSelectedIds().size()>0) - menu.addAction (mRevertAction); + /// \todo Reverting temporarily disabled on tables that support reordering, because + /// revert logic currently can not handle reordering. + if (mModel->getReordering()==CSMWorld::IdTable::Reordering_None) + if (listRevertableSelectedIds().size()>0) + menu.addAction (mRevertAction); if (listDeletableSelectedIds().size()>0) menu.addAction (mDeleteAction); From 2af7f60488942d6cd0fa01a57ba40e8fcc87eeeb Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 14 Nov 2013 12:21:10 +0100 Subject: [PATCH 259/434] flag reordered records as modified --- apps/opencs/model/world/collection.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/opencs/model/world/collection.hpp b/apps/opencs/model/world/collection.hpp index 3f410e3971..df144ba7bf 100644 --- a/apps/opencs/model/world/collection.hpp +++ b/apps/opencs/model/world/collection.hpp @@ -176,7 +176,10 @@ namespace CSMWorld std::vector > buffer (size); for (int i=0; i Date: Thu, 14 Nov 2013 12:22:24 +0100 Subject: [PATCH 260/434] silenced a warning --- apps/openmw/mwgui/inventoryitemmodel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/inventoryitemmodel.cpp b/apps/openmw/mwgui/inventoryitemmodel.cpp index 712e1b6c64..1263edd978 100644 --- a/apps/openmw/mwgui/inventoryitemmodel.cpp +++ b/apps/openmw/mwgui/inventoryitemmodel.cpp @@ -56,7 +56,7 @@ void InventoryItemModel::removeItem (const ItemStack& item, size_t count) if (removed == 0) throw std::runtime_error("Item to remove not found in container store"); - else if (removed < count) + else if (removed < static_cast (count)) throw std::runtime_error("Not enough items in the stack to remove"); } From dcfff7d457d913b4f694d983e06e0ee7be6a1907 Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Thu, 14 Nov 2013 12:32:58 +0100 Subject: [PATCH 261/434] Correcting some epic fail. --- manual/opencs/windows.tex | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/manual/opencs/windows.tex b/manual/opencs/windows.tex index b5b34e7881..3bdfa63626 100644 --- a/manual/opencs/windows.tex +++ b/manual/opencs/windows.tex @@ -5,17 +5,17 @@ OpenCS windows interface is easy to describe and understand. In fact We decided Because of this, and the fact that we expect that user is familiar with other applications using windows this section is mostly focused on practical ways of organizing work with the OpenCS. \subsection{Basics} -After starting Open{CS} and choosing content files to use a editor window should show up. It probably does not look surprising: there is a menubar at the top, and there is a large empty area. That's it: a brand new Open{CS} window contains only menubar and statusbar. In order to make it a little bit more useful you probably want to enable some name panels\footnote{Also known as widgets.}. You are free to do so, just try to explore the menubar. \\ +After starting Open{CS} and choosing content files to use a editor window should show up. It probably does not look surprising: there is a menubar at the top, and there is a large empty area. That's it: a brand new Open{CS} window contains only menubar and statusbar. In order to make it a little bit more useful you probably want to enable some panels\footnote{Also known as widgets.}. You are free to do so, just try to explore the menubar. \\ You probably founded out the way to enable and disable some interesting tables, but those will be described later. For now, let's just focus on the windows itself. \paragraph{Creating new windows} -is easy! Just visit view menu, and use the ``New View'' item. Suddenly, out of the blue a new window will show up. As you would expect, it is also blank, and you are free to add any of the Open{CS} name panels. +is easy! Just visit view menu, and use the ``New View'' item. Suddenly, out of the blue a new window will show up. As you would expect, it is also blank, and you are free to add any of the Open{CS} panels. \paragraph{Closing opened window} is also easy! Simply close that window decoration button. We suspect that you knew that already, but better to be sure. Closing last Open{CS} window will also terminate application session. \paragraph{Multi-everything} -is the main foundation of Open{CS} interface. You are free to create as many windows as you want to, free to populate it with any name panels you may want to, and move everything as you wish to -- even if it makes no sense at all. If you just got crazy idea and you are wonder if you are able to have one hundred Open{CS} windows showing name panels of the same type, well most likely you are able to do so.\\ +is the main foundation of Open{CS} interface. You are free to create as many windows as you want to, free to populate it with any panels you may want to, and move everything as you wish to -- even if it makes no sense at all. If you just got crazy idea and you are wonder if you are able to have one hundred Open{CS} windows showing panels of the same type, well most likely you are able to do so.\\ The principle behind this design decision is easy to see for Bethesda made editor, but maybe not so clear for users who are just about to begin their wonderful journey of modding.\\ @@ -24,6 +24,6 @@ So why? Why this is created in such manner. The answer is frankly simple: becaus Open{CS} is designed to simply make sense and do not slowdown users. It is as simple as possible (but not simpler), and uses one flexible approach in all cases.\\ -There is no point in digging deeper in the windows of Open{CS}. Let's explore name panels, starting with tables. +There is no point in digging deeper in the windows of Open{CS}. Let's explore panels, starting with tables. %We should write some tips and tricks here. \ No newline at end of file From 1c020b74c88ec5e50e368b26d4a0d153910cb939 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Thu, 14 Nov 2013 13:14:37 +0100 Subject: [PATCH 262/434] hid ID column in info tables (not relevant for the user) --- apps/opencs/model/world/data.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 6dd3947f63..04e35cdaaf 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -160,7 +160,7 @@ CSMWorld::Data::Data() : mRefs (mCells) mJournals.addColumn (new RecordStateColumn); mJournals.addColumn (new DialogueTypeColumn (true)); - mTopicInfos.addColumn (new StringIdColumn); + mTopicInfos.addColumn (new StringIdColumn (true)); mTopicInfos.addColumn (new RecordStateColumn); mTopicInfos.addColumn (new TopicColumn (false)); mTopicInfos.addColumn (new ActorColumn); @@ -176,7 +176,7 @@ CSMWorld::Data::Data() : mRefs (mCells) mTopicInfos.addColumn (new SoundFileColumn); mTopicInfos.addColumn (new ResponseColumn); - mJournalInfos.addColumn (new StringIdColumn); + mJournalInfos.addColumn (new StringIdColumn (true)); mJournalInfos.addColumn (new RecordStateColumn); mJournalInfos.addColumn (new TopicColumn (true)); mJournalInfos.addColumn (new QuestStatusTypeColumn); From 0b5f5351b5d4156f26f8a240b4db38f100c06a96 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 14 Nov 2013 13:30:48 +0100 Subject: [PATCH 263/434] Fix continuous FX getting removed instantly --- apps/openmw/mwmechanics/actors.cpp | 7 +++++ apps/openmw/mwmechanics/character.cpp | 2 -- apps/openmw/mwmechanics/character.hpp | 5 ++-- apps/openmw/mwmechanics/spells.cpp | 4 ++- apps/openmw/mwrender/animation.cpp | 3 ++- apps/openmw/mwrender/animation.hpp | 1 + apps/openmw/mwworld/inventorystore.cpp | 36 ++++++++++++++------------ 7 files changed, 36 insertions(+), 22 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 6cb0dc55a3..3d52ce8e69 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -374,6 +374,13 @@ namespace MWMechanics if(!paused) { + // Note: we need to do this before any of the animations are updated. + // Reaching the text keys may trigger Hit / Spellcast (and as such, particles), + // so updating VFX immediately after that would just remove the particle effects instantly. + // There needs to be a magic effect update in between. + for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter) + iter->second->updateContinuousVfx(); + for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter) iter->second->update(duration); } diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 0a13f7c8f3..94aceba381 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -716,8 +716,6 @@ void CharacterController::update(float duration) const MWWorld::Class &cls = MWWorld::Class::get(mPtr); Ogre::Vector3 movement(0.0f); - updateContinuousVfx(); - if(!cls.isActor()) { if(mAnimQueue.size() > 1) diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index c82b29b479..0b55534a60 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -173,12 +173,13 @@ class CharacterController bool updateNpcState(bool onground, bool inwater, bool isrunning, bool sneak); - void updateContinuousVfx(); - public: CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim); virtual ~CharacterController(); + // Be careful when to call this, see comment in Actors + void updateContinuousVfx(); + void updatePtr(const MWWorld::Ptr &ptr); void update(float duration); diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index caf3c64818..df1f6a3182 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -32,7 +32,7 @@ namespace MWMechanics std::vector random; random.resize(spell->mEffects.mList.size()); - for (int i=0; i (std::rand()) / RAND_MAX; mSpells.insert (std::make_pair (spellId, random)); } @@ -51,6 +51,8 @@ namespace MWMechanics MagicEffects Spells::getMagicEffects() const { + // TODO: These are recalculated every frame, no need to do that + MagicEffects effects; for (TIterator iter = mSpells.begin(); iter!=mSpells.end(); ++iter) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index d92912fa95..cc92e62489 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -998,7 +998,7 @@ void Animation::addEffect(const std::string &model, int effectId, bool loop, con { // Early out if we already have this effect for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ++it) - if (it->mEffectId == effectId) + if (it->mLoop && loop && it->mEffectId == effectId && it->mBoneName == bonename) return; EffectParams params; @@ -1006,6 +1006,7 @@ void Animation::addEffect(const std::string &model, int effectId, bool loop, con params.mObjects = NifOgre::Loader::createObjects(mInsert, model); params.mLoop = loop; params.mEffectId = effectId; + params.mBoneName = bonename; for(size_t i = 0;i < params.mObjects.mControllers.size();i++) { diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 11adf0c588..b1572b6a16 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -115,6 +115,7 @@ protected: NifOgre::ObjectList mObjects; int mEffectId; bool mLoop; + std::string mBoneName; }; std::vector mEffects; diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 7a5e59f36d..d032b26139 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -308,25 +308,29 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) // so it doesn't really matter if both items will get the same magnitude. *Extreme* edge case. mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID] = random; - // Only the sound of the first effect plays - if (effectIt == enchantment.mEffects.mList.begin()) + // TODO: What do we do if no animation yet? + if (MWBase::Environment::get().getWorld()->getAnimation(actor)) { - static const std::string schools[] = { - "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" - }; + // Only the sound of the first effect plays + if (effectIt == enchantment.mEffects.mList.begin()) + { + static const std::string schools[] = { + "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" + }; - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - if(!magicEffect->mHitSound.empty()) - sndMgr->playSound3D(actor, magicEffect->mHitSound, 1.0f, 1.0f); - else - sndMgr->playSound3D(actor, schools[magicEffect->mData.mSchool]+" hit", 1.0f, 1.0f); - } + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + if(!magicEffect->mHitSound.empty()) + sndMgr->playSound3D(actor, magicEffect->mHitSound, 1.0f, 1.0f); + else + sndMgr->playSound3D(actor, schools[magicEffect->mData.mSchool]+" hit", 1.0f, 1.0f); + } - if (!magicEffect->mHit.empty()) - { - const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get().find (magicEffect->mHit); - bool loop = magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx; - MWBase::Environment::get().getWorld()->getAnimation(actor)->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, ""); + if (!magicEffect->mHit.empty()) + { + const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get().find (magicEffect->mHit); + bool loop = magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx; + MWBase::Environment::get().getWorld()->getAnimation(actor)->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, ""); + } } } From 992a8e9c36046535809402b5a1714470f89120ab Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 14 Nov 2013 14:41:10 +0100 Subject: [PATCH 264/434] Refactor NpcAnimation: get rid of delayed update (no longer required), make sure that the Animation is set up *before* the inventory store is accessed anywhere (which now triggers auto equip and animation update). Allows better tracking of magic VFX for permanent enchantments in InventoryStore. --- apps/openmw/engine.cpp | 5 +- apps/openmw/mwclass/npc.cpp | 2 +- apps/openmw/mwgui/alchemywindow.cpp | 4 +- apps/openmw/mwgui/inventorywindow.cpp | 4 - apps/openmw/mwgui/itemview.cpp | 1 - apps/openmw/mwgui/spellwindow.cpp | 2 - apps/openmw/mwgui/windowmanagerimp.cpp | 6 +- .../mwmechanics/mechanicsmanagerimp.cpp | 2 +- .../mwmechanics/mechanicsmanagerimp.hpp | 4 +- apps/openmw/mwrender/actors.cpp | 7 +- apps/openmw/mwrender/actors.hpp | 2 +- apps/openmw/mwrender/characterpreview.cpp | 10 +- apps/openmw/mwrender/npcanimation.cpp | 94 +++++++------------ apps/openmw/mwrender/npcanimation.hpp | 21 +---- apps/openmw/mwrender/renderingmanager.cpp | 12 +-- apps/openmw/mwworld/inventorystore.cpp | 22 +++-- apps/openmw/mwworld/inventorystore.hpp | 2 + apps/openmw/mwworld/scene.cpp | 6 +- apps/openmw/mwworld/worldimp.cpp | 3 +- 19 files changed, 86 insertions(+), 123 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 3c4714d55e..4a3c418f6c 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -410,13 +410,16 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) mVerboseScripts, *mScriptContext)); // Create game mechanics system - mEnvironment.setMechanicsManager (new MWMechanics::MechanicsManager); + MWMechanics::MechanicsManager* mechanics = new MWMechanics::MechanicsManager; + mEnvironment.setMechanicsManager (mechanics); // Create dialog system mEnvironment.setJournal (new MWDialogue::Journal); mEnvironment.setDialogueManager (new MWDialogue::DialogueManager (mExtensions, mVerboseScripts, mTranslationDataStorage)); mEnvironment.getWorld()->renderPlayer(); + mechanics->buildPlayer(); + window->updatePlayer(); if (!mNewGame) { diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 7862995801..35a6d17a3b 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -254,7 +254,7 @@ namespace MWClass void Npc::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { - renderingInterface.getActors().insertNPC(ptr, getInventoryStore(ptr)); + renderingInterface.getActors().insertNPC(ptr); } void Npc::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const diff --git a/apps/openmw/mwgui/alchemywindow.cpp b/apps/openmw/mwgui/alchemywindow.cpp index 1e203dcd0b..09f692e4f5 100644 --- a/apps/openmw/mwgui/alchemywindow.cpp +++ b/apps/openmw/mwgui/alchemywindow.cpp @@ -47,8 +47,6 @@ namespace MWGui , mIngredients (4) , mSortModel(NULL) { - mAlchemy.setAlchemist (MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); - getWidget(mCreateButton, "CreateButton"); getWidget(mCancelButton, "CancelButton"); getWidget(mIngredients[0], "Ingredient1"); @@ -145,6 +143,8 @@ namespace MWGui void AlchemyWindow::open() { + mAlchemy.setAlchemist (MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); + InventoryItemModel* model = new InventoryItemModel(MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); mSortModel = new SortFilterItemModel(model); mSortModel->setFilter(SortFilterItemModel::Filter_OnlyIngredients); diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index da7ce6557c..8f8744917f 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -63,8 +63,6 @@ namespace MWGui mItemView->eventItemClicked += MyGUI::newDelegate(this, &InventoryWindow::onItemSelected); mItemView->eventBackgroundClicked += MyGUI::newDelegate(this, &InventoryWindow::onBackgroundSelected); - updatePlayer(); - mFilterAll->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onFilterChanged); mFilterWeapon->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onFilterChanged); mFilterApparel->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onFilterChanged); @@ -75,8 +73,6 @@ namespace MWGui setCoord(mPositionInventory.left, mPositionInventory.top, mPositionInventory.width, mPositionInventory.height); onWindowResize(static_cast(mMainWidget)); - - mPreview.setup(); } void InventoryWindow::updatePlayer() diff --git a/apps/openmw/mwgui/itemview.cpp b/apps/openmw/mwgui/itemview.cpp index f9a900ebac..3be133a3c7 100644 --- a/apps/openmw/mwgui/itemview.cpp +++ b/apps/openmw/mwgui/itemview.cpp @@ -45,7 +45,6 @@ void ItemView::setModel(ItemModel *model) { delete mModel; mModel = model; - update(); } void ItemView::initialiseOverride() diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp index e9b2c0fa8a..61ac2c7b29 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -51,8 +51,6 @@ namespace MWGui setCoord(498, 300, 302, 300); - updateSpells(); - mMainWidget->castType()->eventWindowChangeCoord += MyGUI::newDelegate(this, &SpellWindow::onWindowResize); } diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 76ae65eb9b..83325de23a 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -246,9 +246,6 @@ namespace MWGui mPlayerSkillValues.insert(std::make_pair(ESM::Skill::sSkillIds[i], MWMechanics::Stat())); } - unsetSelectedSpell(); - unsetSelectedWeapon(); - // Set up visibility updateVisible(); @@ -1333,6 +1330,9 @@ namespace MWGui void WindowManager::updatePlayer() { + unsetSelectedSpell(); + unsetSelectedWeapon(); + mInventoryWindow->updatePlayer(); } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 54b02fb529..bbebcd6939 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -167,7 +167,7 @@ namespace MWMechanics : mUpdatePlayer (true), mClassSelected (false), mRaceSelected (false) { - buildPlayer(); + //buildPlayer no longer here, needs to be done explicitely after all subsystems are up and running } void MechanicsManager::add(const MWWorld::Ptr& ptr) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index ad07562c7b..aedb84b29c 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -33,12 +33,12 @@ namespace MWMechanics Objects mObjects; Actors mActors; + public: + void buildPlayer(); ///< build player according to stored class/race/birthsign information. Will /// default to the values of the ESM::NPC object, if no explicit information is given. - public: - MechanicsManager(); virtual void add (const MWWorld::Ptr& ptr); diff --git a/apps/openmw/mwrender/actors.cpp b/apps/openmw/mwrender/actors.cpp index 566b6fa81e..1bdec6e193 100644 --- a/apps/openmw/mwrender/actors.cpp +++ b/apps/openmw/mwrender/actors.cpp @@ -68,13 +68,16 @@ void Actors::insertBegin(const MWWorld::Ptr &ptr) ptr.getRefData().setBaseNode(insert); } -void Actors::insertNPC(const MWWorld::Ptr& ptr, MWWorld::InventoryStore& inv) +void Actors::insertNPC(const MWWorld::Ptr& ptr) { insertBegin(ptr); - NpcAnimation* anim = new NpcAnimation(ptr, ptr.getRefData().getBaseNode(), inv, RV_Actors); + NpcAnimation* anim = new NpcAnimation(ptr, ptr.getRefData().getBaseNode(), RV_Actors); delete mAllActors[ptr]; mAllActors[ptr] = anim; mRendering->addWaterRippleEmitter (ptr); + + // Create CustomData, will do autoEquip and trigger animation parts update + ptr.getClass().getInventoryStore(ptr); } void Actors::insertCreature (const MWWorld::Ptr& ptr) { diff --git a/apps/openmw/mwrender/actors.hpp b/apps/openmw/mwrender/actors.hpp index 4547db9ad2..61a0808f57 100644 --- a/apps/openmw/mwrender/actors.hpp +++ b/apps/openmw/mwrender/actors.hpp @@ -39,7 +39,7 @@ namespace MWRender void setRootNode(Ogre::SceneNode* root); - void insertNPC(const MWWorld::Ptr& ptr, MWWorld::InventoryStore& inv); + void insertNPC(const MWWorld::Ptr& ptr); void insertCreature (const MWWorld::Ptr& ptr); void insertActivator (const MWWorld::Ptr& ptr); bool deleteObject (const MWWorld::Ptr& ptr); diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index e42218bc2d..17fca8df3a 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -70,8 +70,9 @@ namespace MWRender mNode = renderRoot->createChildSceneNode(); - mAnimation = new NpcAnimation(mCharacter, mNode, MWWorld::Class::get(mCharacter).getInventoryStore(mCharacter), + mAnimation = new NpcAnimation(mCharacter, mNode, 0, (renderHeadOnly() ? NpcAnimation::VM_HeadOnly : NpcAnimation::VM_Normal)); + mAnimation->updateParts(); Ogre::Vector3 scale = mNode->getScale(); mCamera->setPosition(mPosition * scale); @@ -112,10 +113,9 @@ namespace MWRender { assert(mAnimation); delete mAnimation; - mAnimation = 0; - - mAnimation = new NpcAnimation(mCharacter, mNode, MWWorld::Class::get(mCharacter).getInventoryStore(mCharacter), + mAnimation = new NpcAnimation(mCharacter, mNode, 0, (renderHeadOnly() ? NpcAnimation::VM_HeadOnly : NpcAnimation::VM_Normal)); + mAnimation->updateParts(); float scale=1.f; MWWorld::Class::get(mCharacter).adjustScale(mCharacter, scale); @@ -193,7 +193,7 @@ namespace MWRender else if(mAnimation->getInfo("torch")) mAnimation->disable("torch"); - mAnimation->updateParts(true); + mAnimation->updateParts(); mAnimation->runAnimation(0.0f); mViewport->setDimensions (0, 0, std::min(1.f, float(sizeX) / float(512)), std::min(1.f, float(sizeY) / float(1024))); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 9ffe53eabb..2cbc767113 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -64,25 +64,11 @@ NpcAnimation::~NpcAnimation() } -NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, MWWorld::InventoryStore& inv, int visibilityFlags, ViewMode viewMode) +NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, int visibilityFlags, ViewMode viewMode) : Animation(ptr, node), mStateID(-1), - mTimeToChange(0), mVisibilityFlags(visibilityFlags), - mRobe(inv.end()), - mHelmet(inv.end()), - mShirt(inv.end()), - mCuirass(inv.end()), - mGreaves(inv.end()), - mPauldronL(inv.end()), - mPauldronR(inv.end()), - mBoots(inv.end()), - mPants(inv.end()), - mGloveL(inv.end()), - mGloveR(inv.end()), - mSkirtIter(inv.end()), - mWeapon(inv.end()), - mShield(inv.end()), + mViewMode(viewMode), mShowWeapons(false), mFirstPersonOffset(0.f, 0.f, 0.f) @@ -94,8 +80,6 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, MWWor mPartslots[i] = -1; //each slot is empty mPartPriorities[i] = 0; } - - updateNpcBase(); } void NpcAnimation::setViewMode(NpcAnimation::ViewMode viewMode) @@ -173,56 +157,50 @@ void NpcAnimation::updateNpcBase() for(size_t i = 0;i < ESM::PRT_Count;i++) removeIndividualPart((ESM::PartReferenceType)i); - updateParts(true); + updateParts(); } -void NpcAnimation::updateParts(bool forceupdate) +void NpcAnimation::updateParts() { + if (!mSkelBase) + { + // First update? + updateNpcBase(); + return; + } + + const MWWorld::Class &cls = MWWorld::Class::get(mPtr); + MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr); + static const struct { - MWWorld::ContainerStoreIterator NpcAnimation::*mPart; int mSlot; int mBasePriority; } slotlist[] = { // FIXME: Priority is based on the number of reserved slots. There should be a better way. - { &NpcAnimation::mRobe, MWWorld::InventoryStore::Slot_Robe, 12 }, - { &NpcAnimation::mSkirtIter, MWWorld::InventoryStore::Slot_Skirt, 3 }, - { &NpcAnimation::mHelmet, MWWorld::InventoryStore::Slot_Helmet, 0 }, - { &NpcAnimation::mCuirass, MWWorld::InventoryStore::Slot_Cuirass, 0 }, - { &NpcAnimation::mGreaves, MWWorld::InventoryStore::Slot_Greaves, 0 }, - { &NpcAnimation::mPauldronL, MWWorld::InventoryStore::Slot_LeftPauldron, 0 }, - { &NpcAnimation::mPauldronR, MWWorld::InventoryStore::Slot_RightPauldron, 0 }, - { &NpcAnimation::mBoots, MWWorld::InventoryStore::Slot_Boots, 0 }, - { &NpcAnimation::mGloveL, MWWorld::InventoryStore::Slot_LeftGauntlet, 0 }, - { &NpcAnimation::mGloveR, MWWorld::InventoryStore::Slot_RightGauntlet, 0 }, - { &NpcAnimation::mShirt, MWWorld::InventoryStore::Slot_Shirt, 0 }, - { &NpcAnimation::mPants, MWWorld::InventoryStore::Slot_Pants, 0 }, - { &NpcAnimation::mShield, MWWorld::InventoryStore::Slot_CarriedLeft, 0 }, - { &NpcAnimation::mWeapon, MWWorld::InventoryStore::Slot_CarriedRight, 0 } + { MWWorld::InventoryStore::Slot_Robe, 12 }, + { MWWorld::InventoryStore::Slot_Skirt, 3 }, + { MWWorld::InventoryStore::Slot_Helmet, 0 }, + { MWWorld::InventoryStore::Slot_Cuirass, 0 }, + { MWWorld::InventoryStore::Slot_Greaves, 0 }, + { MWWorld::InventoryStore::Slot_LeftPauldron, 0 }, + { MWWorld::InventoryStore::Slot_RightPauldron, 0 }, + { MWWorld::InventoryStore::Slot_Boots, 0 }, + { MWWorld::InventoryStore::Slot_LeftGauntlet, 0 }, + { MWWorld::InventoryStore::Slot_RightGauntlet, 0 }, + { MWWorld::InventoryStore::Slot_Shirt, 0 }, + { MWWorld::InventoryStore::Slot_Pants, 0 }, + { MWWorld::InventoryStore::Slot_CarriedLeft, 0 }, + { MWWorld::InventoryStore::Slot_CarriedRight, 0 } }; static const size_t slotlistsize = sizeof(slotlist)/sizeof(slotlist[0]); - const MWWorld::Class &cls = MWWorld::Class::get(mPtr); - MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr); - for(size_t i = 0;!forceupdate && i < slotlistsize;i++) - { - MWWorld::ContainerStoreIterator iter = inv.getSlot(slotlist[i].mSlot); - if(this->*slotlist[i].mPart != iter) - { - forceupdate = true; - break; - } - } - if(!forceupdate) - return; - for(size_t i = 0;i < slotlistsize && mViewMode != VM_HeadOnly;i++) { MWWorld::ContainerStoreIterator store = inv.getSlot(slotlist[i].mSlot); - this->*slotlist[i].mPart = store; removePartGroup(slotlist[i].mSlot); - if(this->*slotlist[i].mPart == inv.end()) + if(store == inv.end()) continue; if(slotlist[i].mSlot == MWWorld::InventoryStore::Slot_Helmet) @@ -439,13 +417,6 @@ NifOgre::ObjectList NpcAnimation::insertBoundedPart(const std::string &model, in Ogre::Vector3 NpcAnimation::runAnimation(float timepassed) { - if(mTimeToChange <= 0.0f) - { - mTimeToChange = 0.2f; - updateParts(); - } - mTimeToChange -= timepassed; - Ogre::Vector3 ret = Animation::runAnimation(timepassed); Ogre::SkeletonInstance *baseinst = mSkelBase->getSkeleton(); @@ -599,11 +570,10 @@ void NpcAnimation::showWeapons(bool showWeapon) if(showWeapon) { MWWorld::InventoryStore &inv = MWWorld::Class::get(mPtr).getInventoryStore(mPtr); - mWeapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if(mWeapon != inv.end()) // special case for weapons + MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if(weapon != inv.end()) // special case for weapons { - MWWorld::Ptr weapon = *mWeapon; - std::string mesh = MWWorld::Class::get(weapon).getModel(weapon); + std::string mesh = MWWorld::Class::get(*weapon).getModel(*weapon); addOrReplaceIndividualPart(ESM::PRT_Weapon, MWWorld::InventoryStore::Slot_CarriedRight, 1, mesh); } } diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index b1abf97af0..5bd29cff60 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -43,22 +43,6 @@ private: ViewMode mViewMode; bool mShowWeapons; - float mTimeToChange; - MWWorld::ContainerStoreIterator mRobe; - MWWorld::ContainerStoreIterator mHelmet; - MWWorld::ContainerStoreIterator mShirt; - MWWorld::ContainerStoreIterator mCuirass; - MWWorld::ContainerStoreIterator mGreaves; - MWWorld::ContainerStoreIterator mPauldronL; - MWWorld::ContainerStoreIterator mPauldronR; - MWWorld::ContainerStoreIterator mBoots; - MWWorld::ContainerStoreIterator mPants; - MWWorld::ContainerStoreIterator mGloveL; - MWWorld::ContainerStoreIterator mGloveR; - MWWorld::ContainerStoreIterator mSkirtIter; - MWWorld::ContainerStoreIterator mWeapon; - MWWorld::ContainerStoreIterator mShield; - int mVisibilityFlags; int mPartslots[ESM::PRT_Count]; //Each part slot is taken by clothing, armor, or is empty @@ -78,8 +62,7 @@ private: void addPartGroup(int group, int priority, const std::vector &parts); public: - NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, - MWWorld::InventoryStore& inv, int visibilityFlags, + NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, int visibilityFlags, ViewMode viewMode=VM_Normal); virtual ~NpcAnimation(); @@ -89,7 +72,7 @@ public: void setViewMode(ViewMode viewMode); - void updateParts(bool forceupdate = false); + void updateParts(); /// \brief Applies a translation to the arms and hands. /// This may be called multiple times before the animation diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 817a2d236d..531e64629e 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -336,6 +336,7 @@ void RenderingManager::updateAnimParts(const MWWorld::Ptr& ptr) else if(MWWorld::Class::get(ptr).isActor()) anim = dynamic_cast(mActors.getAnimation(ptr)); + assert(anim); if(anim) anim->updateParts(); } @@ -926,18 +927,17 @@ void RenderingManager::renderPlayer(const MWWorld::Ptr &ptr) { if(!mPlayerAnimation) { - mPlayerAnimation = new NpcAnimation(ptr, ptr.getRefData().getBaseNode(), - MWWorld::Class::get(ptr).getInventoryStore(ptr), - RV_Actors); + mPlayerAnimation = new NpcAnimation(ptr, ptr.getRefData().getBaseNode(), RV_Actors); } else { // Reconstruct the NpcAnimation in-place mPlayerAnimation->~NpcAnimation(); - new(mPlayerAnimation) NpcAnimation(ptr, ptr.getRefData().getBaseNode(), - MWWorld::Class::get(ptr).getInventoryStore(ptr), - RV_Actors); + new(mPlayerAnimation) NpcAnimation(ptr, ptr.getRefData().getBaseNode(), RV_Actors); } + // Ensure CustomData -> autoEquip -> animation update + ptr.getClass().getInventoryStore(ptr); + mCamera->setAnimation(mPlayerAnimation); mWater->removeEmitter(ptr); mWater->addEmitter(ptr); diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index d032b26139..456c07202c 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -45,6 +45,7 @@ void MWWorld::InventoryStore::initSlots (TSlots& slots_) MWWorld::InventoryStore::InventoryStore() : mSelectedEnchantItem(end()) , mUpdatesEnabled (true) + , mFirstAutoEquip(true) { initSlots (mSlots); } @@ -54,6 +55,7 @@ MWWorld::InventoryStore::InventoryStore (const InventoryStore& store) , mSelectedEnchantItem(end()) { mMagicEffects = store.mMagicEffects; + mFirstAutoEquip = store.mFirstAutoEquip; mSelectedEnchantItem = store.mSelectedEnchantItem; mPermanentMagicEffectMagnitudes = store.mPermanentMagicEffectMagnitudes; copySlots (store); @@ -62,6 +64,7 @@ MWWorld::InventoryStore::InventoryStore (const InventoryStore& store) MWWorld::InventoryStore& MWWorld::InventoryStore::operator= (const InventoryStore& store) { mMagicEffects = store.mMagicEffects; + mFirstAutoEquip = store.mFirstAutoEquip; mPermanentMagicEffectMagnitudes = store.mPermanentMagicEffectMagnitudes; ContainerStore::operator= (store); mSlots.clear(); @@ -256,6 +259,7 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& npc) updateMagicEffects(npc); flagAsModified(); } + mFirstAutoEquip = false; } const MWMechanics::MagicEffects& MWWorld::InventoryStore::getMagicEffects() const @@ -308,8 +312,10 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) // so it doesn't really matter if both items will get the same magnitude. *Extreme* edge case. mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID] = random; - // TODO: What do we do if no animation yet? - if (MWBase::Environment::get().getWorld()->getAnimation(actor)) + // During first auto equip, we don't play any sounds. + // Basically we don't want sounds when the actor is first loaded, + // the items should appear as if they'd always been equipped. + if (!mFirstAutoEquip) { // Only the sound of the first effect plays if (effectIt == enchantment.mEffects.mList.begin()) @@ -324,13 +330,15 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) else sndMgr->playSound3D(actor, schools[magicEffect->mData.mSchool]+" hit", 1.0f, 1.0f); } + } - if (!magicEffect->mHit.empty()) - { - const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get().find (magicEffect->mHit); - bool loop = magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx; + if (!magicEffect->mHit.empty()) + { + const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get().find (magicEffect->mHit); + bool loop = magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx; + // Similar as above, we don't want particles during first autoequip either, unless they're continuous. + if (!mFirstAutoEquip || loop) MWBase::Environment::get().getWorld()->getAnimation(actor)->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, ""); - } } } diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index bccdcb991c..f38e340ca1 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -49,6 +49,8 @@ namespace MWWorld // This is disabled during autoequip to avoid excessive updates bool mUpdatesEnabled; + bool mFirstAutoEquip; + // Vanilla allows permanent effects with a random magnitude, so it needs to be stored here. // We also need this to only play sounds and particle effects when the item is equipped, rather than on every update. typedef std::map > TEffectMagnitudes; diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 0c98ca504f..254ad98cfb 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -118,8 +118,6 @@ namespace MWWorld void Scene::loadCell (Ptr::CellStore *cell, Loading::Listener* loadingListener) { - // register local scripts - MWBase::Environment::get().getWorld()->getLocalScripts().addCell (cell); std::pair result = mActiveCells.insert(cell); if(result.second) @@ -157,6 +155,10 @@ namespace MWWorld mRendering.requestMap(cell); mRendering.configureAmbient(*cell); } + + // register local scripts + // ??? Should this go into the above if block ??? + MWBase::Environment::get().getWorld()->getLocalScripts().addCell (cell); } void Scene::playerCellChange(MWWorld::CellStore *cell, const ESM::Position& pos, bool adjustPlayerPos) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index bdd5d58c8a..dd29c33a94 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2239,7 +2239,6 @@ namespace MWWorld void World::updateAnimParts(const Ptr& actor) { - if (actor.mCell && actor.mCell == mWorldScene->getCurrentCell()) - mRendering->updateAnimParts(actor); + mRendering->updateAnimParts(actor); } } From e26cc31e3b02384ea21793490eb4b1021d29172e Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 14 Nov 2013 14:45:08 +0100 Subject: [PATCH 265/434] Disable projectiles for now --- apps/openmw/mwworld/worldimp.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index dd29c33a94..577345c46a 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2199,6 +2199,8 @@ namespace MWWorld if (projectileModel.empty()) return; + return; + MWWorld::ManualRef ref(getStore(), projectileModel); ESM::Position pos; pos.pos[0] = actor.getRefData().getPosition().pos[0]; From 9c5847e2f4501956b13aec752cf0383967f19abb Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 14 Nov 2013 14:52:30 +0100 Subject: [PATCH 266/434] Fix VFX not getting removed when an actor dies --- apps/openmw/mwmechanics/character.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 94aceba381..76bbafb22b 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1114,7 +1114,8 @@ void CharacterController::updateContinuousVfx() for (std::vector::iterator it = effects.begin(); it != effects.end(); ++it) { - if (mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(MWMechanics::EffectKey(*it)).mMagnitude <= 0) + if (mPtr.getClass().getCreatureStats(mPtr).isDead() + || mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(MWMechanics::EffectKey(*it)).mMagnitude <= 0) mAnimation->removeEffect(*it); } } From 5458207325d10092bd3645f3ef2f66c379985ea6 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 14 Nov 2013 18:41:34 +0100 Subject: [PATCH 267/434] Reduce fatigue when casting. Has no effect in the vanilla game due to the GMSTs being 0. --- apps/openmw/mwworld/worldimp.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 577345c46a..e98c965f90 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2039,6 +2039,15 @@ namespace MWWorld { const ESM::Spell* spell = getStore().get().search(selectedSpell); + // Reduce fatigue (note that in the vanilla game, both GMSTs are 0, and there's no fatigue loss) + static const float fFatigueSpellBase = getStore().get().find("fFatigueSpellBase")->getFloat(); + static const float fFatigueSpellMult = getStore().get().find("fFatigueSpellMult")->getFloat(); + MWMechanics::DynamicStat fatigue = stats.getFatigue(); + const float normalizedEncumbrance = actor.getClass().getEncumbrance(actor) / actor.getClass().getCapacity(actor); + float fatigueLoss = spell->mData.mCost * (fFatigueSpellBase + normalizedEncumbrance * fFatigueSpellMult); + fatigue.setCurrent(std::max(0.f, fatigue.getCurrent() - fatigueLoss)); + stats.setFatigue(fatigue); + // Check mana bool fail = false; MWMechanics::DynamicStat magicka = stats.getMagicka(); @@ -2096,7 +2105,8 @@ namespace MWWorld return; } - if (actor == getPlayer().getPlayer()) + // Note: F_Always spells don't contribute to skill progress + if (actor == getPlayer().getPlayer() && !(spell->mData.mFlags & ESM::Spell::F_Always)) actor.getClass().skillUsageSucceeded(actor, MWMechanics::spellSchoolToSkill(MWMechanics::getSpellSchool(selectedSpell, actor)), 0); From 4083c5584802d1aa75d0ea2f7b1a536f9788f6f5 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 14 Nov 2013 19:18:27 +0100 Subject: [PATCH 268/434] Make sure NpcAnimation base exists before runAnimation. Not sure yet why this is needed. --- apps/openmw/mwrender/npcanimation.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 2cbc767113..9353ae2e43 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -417,6 +417,9 @@ NifOgre::ObjectList NpcAnimation::insertBoundedPart(const std::string &model, in Ogre::Vector3 NpcAnimation::runAnimation(float timepassed) { + if (!mSkelBase) + updateNpcBase(); + Ogre::Vector3 ret = Animation::runAnimation(timepassed); Ogre::SkeletonInstance *baseinst = mSkelBase->getSkeleton(); From da85f3e575b5310c46212cb91b6055094491b5c1 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 14 Nov 2013 19:19:32 +0100 Subject: [PATCH 269/434] Use the formula from the wiki for spell success. --- apps/openmw/mwmechanics/spellsuccess.hpp | 131 ++++++++++++----------- apps/openmw/mwworld/worldimp.cpp | 3 +- 2 files changed, 67 insertions(+), 67 deletions(-) diff --git a/apps/openmw/mwmechanics/spellsuccess.hpp b/apps/openmw/mwmechanics/spellsuccess.hpp index 2cb12ffca4..68b89752f8 100644 --- a/apps/openmw/mwmechanics/spellsuccess.hpp +++ b/apps/openmw/mwmechanics/spellsuccess.hpp @@ -1,6 +1,8 @@ #ifndef MWMECHANICS_SPELLSUCCESS_H #define MWMECHANICS_SPELLSUCCESS_H +#include + #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" @@ -27,92 +29,91 @@ namespace MWMechanics return schoolSkillMap[school]; } - inline int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor) - { - NpcStats& stats = MWWorld::Class::get(actor).getNpcStats(actor); - - // determine the spell's school - // this is always the school where the player's respective skill is the least advanced - // out of all the magic effects' schools - const std::vector& effects = spell->mEffects.mList; - int school = -1; - int skillLevel = -1; - for (std::vector::const_iterator it = effects.begin(); - it != effects.end(); ++it) - { - const ESM::MagicEffect* effect = - MWBase::Environment::get().getWorld()->getStore().get().find(it->mEffectID); - int _school = effect->mData.mSchool; - int _skillLevel = stats.getSkill (spellSchoolToSkill(_school)).getModified(); - - if (school == -1) - { - school = _school; - skillLevel = _skillLevel; - } - else if (_skillLevel < skillLevel) - { - school = _school; - skillLevel = _skillLevel; - } - } - - return school; - } - - inline int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor) - { - const ESM::Spell* spell = - MWBase::Environment::get().getWorld()->getStore().get().find(spellId); - return getSpellSchool(spell, actor); - } - - - // UESP wiki / Morrowind/Spells: - // Chance of success is (Spell's skill * 2 + Willpower / 5 + Luck / 10 - Spell cost - Sound magnitude) * (Current fatigue + Maximum Fatigue * 1.5) / Maximum fatigue * 2 /** * @param spell spell to cast * @param actor calculate spell success chance for this actor (depends on actor's skills) + * @param effectiveSchool the spell's effective school (relevant for skill progress) will be written here * @attention actor has to be an NPC and not a creature! * @return success chance from 0 to 100 (in percent) */ - inline float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor) + inline float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool = NULL) { - if (spell->mData.mFlags & ESM::Spell::F_Always // spells with this flag always succeed (usually birthsign spells) - || spell->mData.mType == ESM::Spell::ST_Power) // powers always succeed, but can be cast only once per day - return 100.0; - - if (spell->mEffects.mList.size() == 0) - return 0.0; - NpcStats& stats = MWWorld::Class::get(actor).getNpcStats(actor); CreatureStats& creatureStats = MWWorld::Class::get(actor).getCreatureStats(actor); - int skillLevel = stats.getSkill (spellSchoolToSkill(getSpellSchool(spell, actor))).getModified(); + if (creatureStats.getMagicEffects().get(ESM::MagicEffect::Silence).mMagnitude) + return 0; - // Sound magic effect (reduces spell casting chance) - int soundMagnitude = creatureStats.getMagicEffects().get (MWMechanics::EffectKey (48)).mMagnitude; + if (spell->mData.mType != ESM::Spell::ST_Spell) + return 100; - int willpower = creatureStats.getAttribute(ESM::Attribute::Willpower).getModified(); - int luck = creatureStats.getAttribute(ESM::Attribute::Luck).getModified(); - int currentFatigue = creatureStats.getFatigue().getCurrent(); - int maxFatigue = creatureStats.getFatigue().getModified(); - int spellCost = spell->mData.mCost; + if (spell->mData.mFlags & ESM::Spell::F_Always) + return 100; - // There we go, all needed variables are there, lets go - float chance = (float(skillLevel * 2) + float(willpower)/5.0 + float(luck)/ 10.0 - spellCost - soundMagnitude) * (float(currentFatigue + maxFatigue * 1.5)) / float(maxFatigue * 2.0); + float y = FLT_MAX; + float lowestSkill = 0; - chance = std::max(0.0f, std::min(100.0f, chance)); // clamp to 0 .. 100 + for (std::vector::const_iterator it = spell->mEffects.mList.begin(); it != spell->mEffects.mList.end(); ++it) + { + float x = it->mDuration; + const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find( + it->mEffectID); + if (!(magicEffect->mData.mFlags & ESM::MagicEffect::UncappedDamage)) + x = std::max(1.f, x); + x *= 0.1 * magicEffect->mData.mBaseCost; + x *= 0.5 * (it->mMagnMin + it->mMagnMax); + x *= it->mArea * 0.05 * magicEffect->mData.mBaseCost; + if (it->mRange == ESM::RT_Target) + x *= 1.5; + static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore().get().find( + "fEffectCostMult")->getFloat(); + x *= fEffectCostMult; - return chance; + float s = 2 * stats.getSkill(spellSchoolToSkill(magicEffect->mData.mSchool)).getModified(); + if (s - x < y) + { + y = s - x; + if (effectiveSchool) + *effectiveSchool = magicEffect->mData.mSchool; + lowestSkill = s; + } + } + + + int castBonus = -stats.getMagicEffects().get(ESM::MagicEffect::Sound).mMagnitude; + int actorWillpower = stats.getAttribute(ESM::Attribute::Willpower).getModified(); + int actorLuck = stats.getAttribute(ESM::Attribute::Luck).getModified(); + + float castChance = (lowestSkill - spell->mData.mCost + castBonus + 0.2 * actorWillpower + 0.1 * actorLuck) * stats.getFatigueTerm(); + if (MWBase::Environment::get().getWorld()->getGodModeState() && actor.getRefData().getHandle() == "player") + castChance = 100; + + return std::max(0.f, std::min(100.f, castChance)); } - inline float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor) + inline float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool = NULL) { const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(spellId); - return getSpellSuccessChance(spell, actor); + return getSpellSuccessChance(spell, actor, effectiveSchool); } + + /// @note this only works for ST_Spell + inline int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor) + { + int school = 0; + getSpellSuccessChance(spellId, actor, &school); + return school; + } + + /// @note this only works for ST_Spell + inline int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor) + { + int school = 0; + getSpellSuccessChance(spell, actor, &school); + return school; + } + } #endif diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index e98c965f90..13b8bd79ba 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2105,8 +2105,7 @@ namespace MWWorld return; } - // Note: F_Always spells don't contribute to skill progress - if (actor == getPlayer().getPlayer() && !(spell->mData.mFlags & ESM::Spell::F_Always)) + if (actor == getPlayer().getPlayer() && spell->mData.mType == ESM::Spell::ST_Spell) actor.getClass().skillUsageSucceeded(actor, MWMechanics::spellSchoolToSkill(MWMechanics::getSpellSchool(selectedSpell, actor)), 0); From 49125fa26e5427c031ee9fcea22d38343cb0d8cd Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 14 Nov 2013 19:54:00 +0100 Subject: [PATCH 270/434] Fix restacking unequipped items --- apps/openmw/mwworld/inventorystore.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 456c07202c..f25a3edff8 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -443,6 +443,9 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, c { ContainerStoreIterator retval = it; + // empty this slot + mSlots[slot] = end(); + if (restack) { // restack item previously in this slot for (MWWorld::ContainerStoreIterator iter (begin()); iter != end(); ++iter) @@ -457,9 +460,6 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, c } } - // empty this slot - mSlots[slot] = end(); - if (actor.getRefData().getHandle() == "player") { // Unset OnPCEquip Variable on item's script, if it has a script with that variable declared From 427de69b79f9673e9b94accffb615ba9ebccd5cc Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 14 Nov 2013 21:02:37 +0100 Subject: [PATCH 271/434] Allow stacking enchanted items when charge is full --- apps/openmw/mwworld/containerstore.cpp | 34 ++++++++++++++++++-------- apps/openmw/mwworld/containerstore.hpp | 3 +++ 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index 2aee240894..6e89393dc8 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -77,25 +77,39 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::end() return ContainerStoreIterator (this); } +void MWWorld::ContainerStore::unstack(const Ptr &ptr, const Ptr& container) +{ + if (ptr.getRefData().getCount() <= 1) + return; + addNewStack(ptr)->getRefData().setCount(ptr.getRefData().getCount()-1); + remove(ptr, ptr.getRefData().getCount()-1, container); +} + bool MWWorld::ContainerStore::stacks(const Ptr& ptr1, const Ptr& ptr2) { const MWWorld::Class& cls1 = MWWorld::Class::get(ptr1); const MWWorld::Class& cls2 = MWWorld::Class::get(ptr2); - /// \todo add current enchantment charge here when it is implemented + if (!Misc::StringUtils::ciEqual(ptr1.getCellRef().mRefID, ptr2.getCellRef().mRefID)) + return false; + + // If it has an enchantment, don't stack when some of the charge is already used + if (!ptr1.getClass().getEnchantment(ptr1).empty()) + { + const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find( + ptr1.getClass().getEnchantment(ptr1)); + float maxCharge = enchantment->mData.mCharge; + float enchantCharge1 = ptr1.getCellRef().mEnchantmentCharge == -1 ? maxCharge : ptr1.getCellRef().mEnchantmentCharge; + float enchantCharge2 = ptr2.getCellRef().mEnchantmentCharge == -1 ? maxCharge : ptr2.getCellRef().mEnchantmentCharge; + if (enchantCharge1 != maxCharge || enchantCharge2 != maxCharge) + return false; + } + return ptr1 != ptr2 // an item never stacks onto itself - && Misc::StringUtils::ciEqual(ptr1.getCellRef().mRefID, ptr2.getCellRef().mRefID) && ptr1.getCellRef().mOwner == ptr2.getCellRef().mOwner && ptr1.getCellRef().mSoul == ptr2.getCellRef().mSoul - // item with a script never stacks - && cls1.getScript(ptr1) == "" - && cls2.getScript(ptr2) == "" - - // item with enchantment never stacks (we could revisit this later, - // but for now it makes selecting items in the spell window much easier) - && cls1.getEnchantment(ptr1) == "" - && cls2.getEnchantment(ptr2) == "" + && cls1.getScript(ptr1) == cls2.getScript(ptr2) // item that is already partly used up never stacks && (!cls1.hasItemHealth(ptr1) || ptr1.getCellRef().mCharge == -1 diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index 701553c74d..57b3d831bb 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -85,6 +85,9 @@ namespace MWWorld /// /// @return the number of items actually removed + void unstack (const Ptr& ptr, const Ptr& container); + ///< Unstack an item in this container. The item's count will be set to 1, then a new stack will be added with (origCount-1). + protected: ContainerStoreIterator addNewStack (const Ptr& ptr); ///< Add the item to this container (do not try to stack it onto existing items) From 00af6b5617c06e64f61241444dc88bf7b9747c6c Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 15 Nov 2013 02:08:36 +0100 Subject: [PATCH 272/434] Use an inventory store listener for animation parts and VFX update instead of updating them directly. Slightly more flexible, reduces InventoryStore dependencies and solves a crash during character creation due to the preview doll's animation not being registered in World. --- apps/openmw/mwrender/characterpreview.cpp | 5 +- apps/openmw/mwrender/npcanimation.cpp | 52 +++++++++--- apps/openmw/mwrender/npcanimation.hpp | 27 ++++-- apps/openmw/mwscript/containerextensions.cpp | 5 +- apps/openmw/mwworld/containerstore.cpp | 2 +- apps/openmw/mwworld/containerstore.hpp | 1 - apps/openmw/mwworld/inventorystore.cpp | 89 +++++++++----------- apps/openmw/mwworld/inventorystore.hpp | 28 +++++- 8 files changed, 131 insertions(+), 78 deletions(-) diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 17fca8df3a..b9818efbba 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -71,7 +71,7 @@ namespace MWRender mNode = renderRoot->createChildSceneNode(); mAnimation = new NpcAnimation(mCharacter, mNode, - 0, (renderHeadOnly() ? NpcAnimation::VM_HeadOnly : NpcAnimation::VM_Normal)); + 0, true, (renderHeadOnly() ? NpcAnimation::VM_HeadOnly : NpcAnimation::VM_Normal)); mAnimation->updateParts(); Ogre::Vector3 scale = mNode->getScale(); @@ -102,7 +102,6 @@ namespace MWRender { if (mSceneMgr) { - //Ogre::TextureManager::getSingleton().remove(mName); mSceneMgr->destroyAllCameras(); delete mAnimation; Ogre::Root::getSingleton().destroySceneManager(mSceneMgr); @@ -114,7 +113,7 @@ namespace MWRender assert(mAnimation); delete mAnimation; mAnimation = new NpcAnimation(mCharacter, mNode, - 0, (renderHeadOnly() ? NpcAnimation::VM_HeadOnly : NpcAnimation::VM_Normal)); + 0, true, (renderHeadOnly() ? NpcAnimation::VM_HeadOnly : NpcAnimation::VM_Normal)); mAnimation->updateParts(); float scale=1.f; diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 9353ae2e43..6bc2bfc12a 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -14,6 +14,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/soundmanager.hpp" #include "renderconst.hpp" #include "camera.hpp" @@ -58,17 +59,19 @@ const NpcAnimation::PartBoneMap NpcAnimation::sPartList = createPartListMap(); NpcAnimation::~NpcAnimation() { + if (!mListenerDisabled) + mPtr.getClass().getInventoryStore(mPtr).setListener(NULL); + Ogre::SceneManager *sceneMgr = mInsert->getCreator(); for(size_t i = 0;i < ESM::PRT_Count;i++) destroyObjectList(sceneMgr, mObjectParts[i]); } -NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, int visibilityFlags, ViewMode viewMode) +NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, int visibilityFlags, bool disableListener, ViewMode viewMode) : Animation(ptr, node), - mStateID(-1), mVisibilityFlags(visibilityFlags), - + mListenerDisabled(disableListener), mViewMode(viewMode), mShowWeapons(false), mFirstPersonOffset(0.f, 0.f, 0.f) @@ -80,6 +83,11 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, int v mPartslots[i] = -1; //each slot is empty mPartPriorities[i] = 0; } + + if (!disableListener) + mPtr.getClass().getInventoryStore(mPtr).setListener(this); + + updateNpcBase(); } void NpcAnimation::setViewMode(NpcAnimation::ViewMode viewMode) @@ -162,13 +170,6 @@ void NpcAnimation::updateNpcBase() void NpcAnimation::updateParts() { - if (!mSkelBase) - { - // First update? - updateNpcBase(); - return; - } - const MWWorld::Class &cls = MWWorld::Class::get(mPtr); MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr); @@ -417,9 +418,6 @@ NifOgre::ObjectList NpcAnimation::insertBoundedPart(const std::string &model, in Ogre::Vector3 NpcAnimation::runAnimation(float timepassed) { - if (!mSkelBase) - updateNpcBase(); - Ogre::Vector3 ret = Animation::runAnimation(timepassed); Ogre::SkeletonInstance *baseinst = mSkelBase->getSkeleton(); @@ -586,4 +584,32 @@ void NpcAnimation::showWeapons(bool showWeapon) } } +void NpcAnimation::permanentEffectAdded(const ESM::MagicEffect *magicEffect, bool isNew, bool playSound) +{ + // During first auto equip, we don't play any sounds. + // Basically we don't want sounds when the actor is first loaded, + // the items should appear as if they'd always been equipped. + if (playSound) + { + static const std::string schools[] = { + "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" + }; + + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + if(!magicEffect->mHitSound.empty()) + sndMgr->playSound3D(mPtr, magicEffect->mHitSound, 1.0f, 1.0f); + else + sndMgr->playSound3D(mPtr, schools[magicEffect->mData.mSchool]+" hit", 1.0f, 1.0f); + } + + if (!magicEffect->mHit.empty()) + { + const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get().find (magicEffect->mHit); + bool loop = magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx; + // Don't play particle VFX unless the effect is new or it should be looping. + if (isNew || loop) + addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, ""); + } +} + } diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 5bd29cff60..becd014370 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -3,23 +3,22 @@ #include "animation.hpp" -#include "../mwworld/containerstore.hpp" +#include "../mwworld/inventorystore.hpp" namespace ESM { struct NPC; } -namespace MWWorld -{ - class InventoryStore; -} - namespace MWRender { -class NpcAnimation : public Animation +class NpcAnimation : public Animation, public MWWorld::InventoryStoreListener { +public: + virtual void equipmentChanged() { updateParts(); } + virtual void permanentEffectAdded(const ESM::MagicEffect *magicEffect, bool isNew, bool playSound); + public: typedef std::map PartBoneMap; @@ -32,7 +31,7 @@ public: private: static const PartBoneMap sPartList; - int mStateID; + bool mListenerDisabled; // Bounded Parts NifOgre::ObjectList mObjectParts[ESM::PRT_Count]; @@ -62,7 +61,17 @@ private: void addPartGroup(int group, int priority, const std::vector &parts); public: - NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, int visibilityFlags, + /** + * @param ptr + * @param node + * @param visibilityFlags + * @param disableListener Don't listen for equipment changes and magic effects. InventoryStore only supports + * one listener at a time, so you shouldn't do this if creating several NpcAnimations + * for the same Ptr, eg preview dolls for the player. + * Those need to be manually rendered anyway. + * @param viewMode + */ + NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, int visibilityFlags, bool disableListener = false, ViewMode viewMode=VM_Normal); virtual ~NpcAnimation(); diff --git a/apps/openmw/mwscript/containerextensions.cpp b/apps/openmw/mwscript/containerextensions.cpp index d3ed50838b..d124eca489 100644 --- a/apps/openmw/mwscript/containerextensions.cpp +++ b/apps/openmw/mwscript/containerextensions.cpp @@ -135,7 +135,10 @@ namespace MWScript MWWorld::ContainerStore& store = MWWorld::Class::get (ptr).getContainerStore (ptr); - std::string itemName = ""; + std::string itemName; + for (MWWorld::ContainerStoreIterator iter(store.begin()); iter != store.end(); ++iter) + if (Misc::StringUtils::ciEqual(iter->getCellRef().mRefID, item)) + itemName = iter->getClass().getName(*iter); int numRemoved = store.remove(item, count, ptr); diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index 6e89393dc8..be2e0b5a3d 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -63,7 +63,7 @@ namespace } } -MWWorld::ContainerStore::ContainerStore() : mStateId (0), mCachedWeight (0), mWeightUpToDate (false) {} +MWWorld::ContainerStore::ContainerStore() : mCachedWeight (0), mWeightUpToDate (false) {} MWWorld::ContainerStore::~ContainerStore() {} diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index 57b3d831bb..c430b4bfc5 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -49,7 +49,6 @@ namespace MWWorld MWWorld::CellRefList probes; MWWorld::CellRefList repairs; MWWorld::CellRefList weapons; - int mStateId; mutable float mCachedWeight; mutable bool mWeightUpToDate; ContainerStoreIterator addImp (const Ptr& ptr); diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index f25a3edff8..c2b1f084d5 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -9,9 +9,6 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" -#include "../mwbase/soundmanager.hpp" - -#include "../mwrender/animation.hpp" #include "../mwmechanics/npcstats.hpp" @@ -46,6 +43,7 @@ MWWorld::InventoryStore::InventoryStore() : mSelectedEnchantItem(end()) , mUpdatesEnabled (true) , mFirstAutoEquip(true) + , mListener(NULL) { initSlots (mSlots); } @@ -53,6 +51,8 @@ MWWorld::InventoryStore::InventoryStore() MWWorld::InventoryStore::InventoryStore (const InventoryStore& store) : ContainerStore (store) , mSelectedEnchantItem(end()) + , mListener(NULL) + , mUpdatesEnabled(true) { mMagicEffects = store.mMagicEffects; mFirstAutoEquip = store.mFirstAutoEquip; @@ -122,9 +122,8 @@ void MWWorld::InventoryStore::equip (int slot, const ContainerStoreIterator& ite flagAsModified(); - updateActorModel(actor); - - updateMagicEffects(actor); + fireEquipmentChangedEvent(); + updateMagicEffects(); } void MWWorld::InventoryStore::unequipAll(const MWWorld::Ptr& actor) @@ -255,11 +254,10 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& npc) if (changed) { mSlots.swap (slots_); - updateActorModel(npc); - updateMagicEffects(npc); + fireEquipmentChangedEvent(); + updateMagicEffects(); flagAsModified(); } - mFirstAutoEquip = false; } const MWMechanics::MagicEffects& MWWorld::InventoryStore::getMagicEffects() const @@ -267,12 +265,16 @@ const MWMechanics::MagicEffects& MWWorld::InventoryStore::getMagicEffects() cons return mMagicEffects; } -void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) +void MWWorld::InventoryStore::updateMagicEffects() { // To avoid excessive updates during auto-equip if (!mUpdatesEnabled) return; + // Delay update until the listener is set up + if (!mListener) + return; + mMagicEffects = MWMechanics::MagicEffects(); for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter) @@ -296,6 +298,16 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) for (unsigned int i=0; i (std::rand()) / RAND_MAX; + bool existed = (mPermanentMagicEffectMagnitudes.find((**iter).getCellRef().mRefID) != mPermanentMagicEffectMagnitudes.end()); + if (!existed) + { + // Note that using the RefID as a key here is not entirely correct. + // Consider equipping the same item twice (e.g. a ring) + // However, permanent enchantments with a random magnitude are kind of an exploit anyway, + // so it doesn't really matter if both items will get the same magnitude. *Extreme* edge case. + mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID] = random; + } + int i=0; for (std::vector::const_iterator effectIt (enchantment.mEffects.mList.begin()); effectIt!=enchantment.mEffects.mList.end(); ++effectIt) @@ -304,42 +316,13 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) MWBase::Environment::get().getWorld()->getStore().get().find ( effectIt->mEffectID); - if (mPermanentMagicEffectMagnitudes.find((**iter).getCellRef().mRefID) == mPermanentMagicEffectMagnitudes.end()) + if (!existed) { - // Note that using the RefID as a key here is not entirely correct. - // Consider equipping the same item twice (e.g. a ring) - // However, permanent enchantments with a random magnitude are kind of an exploit anyway, - // so it doesn't really matter if both items will get the same magnitude. *Extreme* edge case. - mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID] = random; - // During first auto equip, we don't play any sounds. // Basically we don't want sounds when the actor is first loaded, // the items should appear as if they'd always been equipped. - if (!mFirstAutoEquip) - { - // Only the sound of the first effect plays - if (effectIt == enchantment.mEffects.mList.begin()) - { - static const std::string schools[] = { - "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" - }; - - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - if(!magicEffect->mHitSound.empty()) - sndMgr->playSound3D(actor, magicEffect->mHitSound, 1.0f, 1.0f); - else - sndMgr->playSound3D(actor, schools[magicEffect->mData.mSchool]+" hit", 1.0f, 1.0f); - } - } - - if (!magicEffect->mHit.empty()) - { - const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get().find (magicEffect->mHit); - bool loop = magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx; - // Similar as above, we don't want particles during first autoequip either, unless they're continuous. - if (!mFirstAutoEquip || loop) - MWBase::Environment::get().getWorld()->getAnimation(actor)->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, ""); - } + mListener->permanentEffectAdded(magicEffect, !mFirstAutoEquip, + !mFirstAutoEquip && effectIt == enchantment.mEffects.mList.begin()); } mMagicEffects.add (*effectIt, random[i]); @@ -367,6 +350,8 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) else ++it; } + + mFirstAutoEquip = false; } void MWWorld::InventoryStore::flagAsModified() @@ -380,7 +365,7 @@ bool MWWorld::InventoryStore::stacks(const Ptr& ptr1, const Ptr& ptr2) if (!canStack) return false; - // don't stack if 'stack' (the item being checked against) is currently equipped. + // don't stack if either item is currently equipped for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter) { @@ -481,8 +466,8 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, c } } - updateActorModel(actor); - updateMagicEffects(actor); + fireEquipmentChangedEvent(); + updateMagicEffects(); return retval; } @@ -502,8 +487,16 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipItem(const MWWor throw std::runtime_error ("attempt to unequip an item that is not currently equipped"); } -void MWWorld::InventoryStore::updateActorModel(const MWWorld::Ptr& actor) +void MWWorld::InventoryStore::setListener(InventoryStoreListener *listener) { - if (mUpdatesEnabled) - MWBase::Environment::get().getWorld()->updateAnimParts(actor); + mListener = listener; + updateMagicEffects(); +} + +void MWWorld::InventoryStore::fireEquipmentChangedEvent() +{ + if (!mUpdatesEnabled) + return; + if (mListener) + mListener->equipmentChanged(); } diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index f38e340ca1..099523a9c8 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -12,6 +12,25 @@ namespace MWMechanics namespace MWWorld { + class InventoryStoreListener + { + public: + /** + * Fired when items are equipped or unequipped + */ + virtual void equipmentChanged () {} + + /** + * @param effect + * @param isNew Is this effect new (e.g. the item for it was just now manually equipped) + * or was it loaded from a savegame / initial game state? \n + * If it isn't new, non-looping VFX should not be played. + * @param playSound Play effect sound? + */ + virtual void permanentEffectAdded (const ESM::MagicEffect *magicEffect, bool isNew, bool playSound) {} + + }; + ///< \brief Variant of the ContainerStore for NPCs class InventoryStore : public ContainerStore { @@ -45,6 +64,8 @@ namespace MWWorld MWMechanics::MagicEffects mMagicEffects; + InventoryStoreListener* mListener; + // Enables updates of magic effects and actor model whenever items are equipped or unequipped. // This is disabled during autoequip to avoid excessive updates bool mUpdatesEnabled; @@ -67,9 +88,9 @@ namespace MWWorld void initSlots (TSlots& slots_); - void updateActorModel (const Ptr& actor); + void updateMagicEffects(); - void updateMagicEffects(const Ptr& actor); + void fireEquipmentChangedEvent(); public: @@ -138,6 +159,9 @@ namespace MWWorld /// @return an iterator to the item that was previously in the slot /// (it can be re-stacked so its count may be different than when it /// was equipped). + + void setListener (InventoryStoreListener* listener); + ///< Set a listener for various events, see \a InventoryStoreListener }; } From f7befa3e54a2c1965169b7303153623412a9a81f Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 15 Nov 2013 02:20:04 +0100 Subject: [PATCH 273/434] Remove a no longer required method --- apps/openmw/mwbase/world.hpp | 2 -- apps/openmw/mwrender/renderingmanager.cpp | 14 -------------- apps/openmw/mwrender/renderingmanager.hpp | 3 --- apps/openmw/mwworld/worldimp.cpp | 6 ------ apps/openmw/mwworld/worldimp.hpp | 2 -- 5 files changed, 27 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index ad3130ac5d..e1adfbec46 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -414,8 +414,6 @@ namespace MWBase virtual void castSpell (const MWWorld::Ptr& actor) = 0; - virtual void updateAnimParts(const MWWorld::Ptr& ptr) = 0; - virtual void launchProjectile (const std::string& id, const ESM::EffectList& effects, const MWWorld::Ptr& actor, const std::string& sourceName) = 0; }; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 531e64629e..57e00d76c7 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -327,20 +327,6 @@ void RenderingManager::rebuildPtr(const MWWorld::Ptr &ptr) } } -void RenderingManager::updateAnimParts(const MWWorld::Ptr& ptr) -{ - NpcAnimation *anim = NULL; - - if(ptr.getRefData().getHandle() == "player") - anim = mPlayerAnimation; - else if(MWWorld::Class::get(ptr).isActor()) - anim = dynamic_cast(mActors.getAnimation(ptr)); - - assert(anim); - if(anim) - anim->updateParts(); -} - void RenderingManager::update (float duration, bool paused) { MWBase::World *world = MWBase::Environment::get().getWorld(); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 8913e0a789..2d08139128 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -141,9 +141,6 @@ public: /// and equipment. void rebuildPtr(const MWWorld::Ptr &ptr); - /// Update actor model parts. - void updateAnimParts(const MWWorld::Ptr &ptr); - void update (float duration, bool paused); void setAmbientColour(const Ogre::ColourValue& colour); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 13b8bd79ba..eca2ebb79f 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2246,10 +2246,4 @@ namespace MWWorld ++it; } } - - - void World::updateAnimParts(const Ptr& actor) - { - mRendering->updateAnimParts(actor); - } } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 456d1491f9..19890b8310 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -490,8 +490,6 @@ namespace MWWorld virtual void castSpell (const MWWorld::Ptr& actor); - virtual void updateAnimParts(const MWWorld::Ptr& ptr); - virtual void launchProjectile (const std::string& id, const ESM::EffectList& effects, const MWWorld::Ptr& actor, const std::string& sourceName); }; From 065f14579fc4d02472212369857f6a6e61add4db Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 15 Nov 2013 02:57:21 +0100 Subject: [PATCH 274/434] Fix a container window regression (oops) --- apps/openmw/mwgui/itemview.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwgui/itemview.cpp b/apps/openmw/mwgui/itemview.cpp index 3be133a3c7..f9a900ebac 100644 --- a/apps/openmw/mwgui/itemview.cpp +++ b/apps/openmw/mwgui/itemview.cpp @@ -45,6 +45,7 @@ void ItemView::setModel(ItemModel *model) { delete mModel; mModel = model; + update(); } void ItemView::initialiseOverride() From d619d0fa2557b9fa4a4d1aeb8ee26262aa7c7efa Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 15 Nov 2013 03:39:25 +0100 Subject: [PATCH 275/434] Don't grab the cursor while the console is opened --- apps/openmw/mwinput/inputmanagerimp.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index f9ea3dfc3e..35487e3391 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -274,7 +274,8 @@ namespace MWInput if (!loading) mInputBinder->update(dt); - bool main_menu = MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu); + bool grab = !MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu) + && MWBase::Environment::get().getWindowManager()->getMode() != MWGui::GM_Console; bool was_relative = mInputManager->getMouseRelative(); bool is_relative = !MWBase::Environment::get().getWindowManager()->isGuiMode(); @@ -284,7 +285,7 @@ namespace MWInput mInputManager->setMouseRelative(is_relative); //we let the mouse escape in the main menu - mInputManager->setGrabPointer(!main_menu); + mInputManager->setGrabPointer(grab); //we switched to non-relative mode, move our cursor to where the in-game //cursor is From 67284e2f9dbec52cd4f9a56d50616a2ecccf2230 Mon Sep 17 00:00:00 2001 From: eroen Date: Fri, 15 Nov 2013 15:59:38 +0100 Subject: [PATCH 276/434] Introduce -DBUILD_WITH_DPKG to toggle dpkg based install The current system automagically chooses between the dpkg-based install method (for debian-derived distributions) and a "traditional" install and sets install paths based on whether cmake can find a 'dpkg' executable. This is not ideal, since dpkg is occasionally installed on linux distributions unrelated to debian for purposes other than package management. In particular, Arch and Gentoo carry it in their repositories. --- CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b85fabf522..b4d1a9a808 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,7 +50,10 @@ option(USE_MPG123 "use mpg123 + libsndfile for sound" ON) # OS X deployment option(OPENMW_OSX_DEPLOYMENT OFF) -find_program(DPKG_PROGRAM dpkg DOC "dpkg program of Debian-based systems") +option(BUILD_WITH_DPKG "enable dpkg-based install for debian and debian derivatives" OFF) +if(BUILD_WITH_DPKG) + find_program(DPKG_PROGRAM dpkg DOC "dpkg program of Debian-based systems") +endif(BUILD_WITH_DPKG) # Location of morrowind data files if (APPLE) From d4b8ac5b499b2a109503937b34861a2e77c44fdd Mon Sep 17 00:00:00 2001 From: eroen Date: Fri, 15 Nov 2013 16:53:05 +0100 Subject: [PATCH 277/434] don't dpkg on windows and macos No need to clutter windows and macos configuration with dpkg options, as it is unlikely to ever be useful. --- CMakeLists.txt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b4d1a9a808..d36898400a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,10 +50,12 @@ option(USE_MPG123 "use mpg123 + libsndfile for sound" ON) # OS X deployment option(OPENMW_OSX_DEPLOYMENT OFF) -option(BUILD_WITH_DPKG "enable dpkg-based install for debian and debian derivatives" OFF) -if(BUILD_WITH_DPKG) - find_program(DPKG_PROGRAM dpkg DOC "dpkg program of Debian-based systems") -endif(BUILD_WITH_DPKG) +if(UNIX AND NOT APPLE) + option(BUILD_WITH_DPKG "enable dpkg-based install for debian and debian derivatives" OFF) + if(BUILD_WITH_DPKG) + find_program(DPKG_PROGRAM dpkg DOC "dpkg program of Debian-based systems") + endif(BUILD_WITH_DPKG) +endif(UNIX AND NOT APPLE) # Location of morrowind data files if (APPLE) From c5f1bbcc5fb41151dd7ea8117b519ad0e849a449 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 15 Nov 2013 19:43:25 +0100 Subject: [PATCH 278/434] Add functions to get the effect affecting resistance and weakness for another effect --- components/esm/loadmgef.cpp | 94 +++++++++++++++++++++++++++++++++++++ components/esm/loadmgef.hpp | 6 +++ 2 files changed, 100 insertions(+) diff --git a/components/esm/loadmgef.cpp b/components/esm/loadmgef.cpp index 1a90f5b09c..46e2a8555d 100644 --- a/components/esm/loadmgef.cpp +++ b/components/esm/loadmgef.cpp @@ -81,6 +81,100 @@ void MagicEffect::save(ESMWriter &esm) const esm.writeHNOString("DESC", mDescription); } +short MagicEffect::getResistanceEffect(short effect) +{ + // Source https://wiki.openmw.org/index.php?title=Research:Magic#Effect_attribute + + // + std::map effects; + effects[DisintegrateArmor] = Sanctuary; + effects[DisintegrateWeapon] = Sanctuary; + + for (int i=0; i<5; ++i) + effects[DrainAttribute+i] = ResistMagicka; + for (int i=0; i<5; ++i) + effects[DamageAttribute+i] = ResistMagicka; + for (int i=0; i<5; ++i) + effects[AbsorbAttribute+i] = ResistMagicka; + for (int i=0; i<10; ++i) + effects[WeaknessToFire+i] = ResistMagicka; + + effects[Burden] = ResistMagicka; + effects[Charm] = ResistMagicka; + effects[Silence] = ResistMagicka; + effects[Blind] = ResistMagicka; + effects[Sound] = ResistMagicka; + + for (int i=0; i<2; ++i) + { + effects[CalmHumanoid] = ResistMagicka; + effects[FrenzyHumanoid] = ResistMagicka; + effects[DemoralizeHumanoid] = ResistMagicka; + effects[RallyHumanoid] = ResistMagicka; + } + + effects[TurnUndead] = ResistMagicka; + + effects[FireDamage] = ResistFire; + effects[FrostDamage] = ResistFrost; + effects[ShockDamage] = ResistShock; + effects[Vampirism] = ResistCommonDisease; + effects[Corprus] = ResistCorprusDisease; + effects[Poison] = ResistPoison; + effects[Paralyze] = ResistParalysis; + + if (effects.find(effect) != effects.end()) + return effects[effect]; + else + return -1; +} + +short MagicEffect::getWeaknessEffect(short effect) +{ + std::map effects; + effects[DisintegrateArmor] = Sanctuary; + effects[DisintegrateWeapon] = Sanctuary; + + for (int i=0; i<5; ++i) + effects[DrainAttribute+i] = WeaknessToMagicka; + for (int i=0; i<5; ++i) + effects[DamageAttribute+i] = WeaknessToMagicka; + for (int i=0; i<5; ++i) + effects[AbsorbAttribute+i] = WeaknessToMagicka; + for (int i=0; i<10; ++i) + effects[WeaknessToFire+i] = WeaknessToMagicka; + + effects[Burden] = WeaknessToMagicka; + effects[Charm] = WeaknessToMagicka; + effects[Silence] = WeaknessToMagicka; + effects[Blind] = WeaknessToMagicka; + effects[Sound] = WeaknessToMagicka; + + for (int i=0; i<2; ++i) + { + effects[CalmHumanoid] = WeaknessToMagicka; + effects[FrenzyHumanoid] = WeaknessToMagicka; + effects[DemoralizeHumanoid] = WeaknessToMagicka; + effects[RallyHumanoid] = WeaknessToMagicka; + } + + effects[TurnUndead] = WeaknessToMagicka; + + effects[FireDamage] = WeaknessToFire; + effects[FrostDamage] = WeaknessToFrost; + effects[ShockDamage] = WeaknessToShock; + effects[Vampirism] = WeaknessToCommonDisease; + effects[Corprus] = WeaknessToCorprusDisease; + effects[Poison] = WeaknessToPoison; + + // Weakness to magicka or -1 ? + effects[Paralyze] = WeaknessToMagicka; + + if (effects.find(effect) != effects.end()) + return effects[effect]; + else + return -1; +} static std::map genNameMap() { diff --git a/components/esm/loadmgef.hpp b/components/esm/loadmgef.hpp index b1047e94af..cc9cc180ee 100644 --- a/components/esm/loadmgef.hpp +++ b/components/esm/loadmgef.hpp @@ -58,6 +58,12 @@ struct MagicEffect static const std::string &effectIdToString(short effectID); static short effectStringToId(const std::string &effect); + + /// Returns the effect that provides resistance against \a effect (or -1 if there's none) + static short getResistanceEffect(short effect); + /// Returns the effect that induces weakness against \a effect (or -1 if there's none) + static short getWeaknessEffect(short effect); + MagnitudeDisplayType getMagnitudeDisplayType() const; From c73217627e1a7533bf181a2731673ae77ea24247 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 15 Nov 2013 20:29:47 +0100 Subject: [PATCH 279/434] Move code for listing effect sources to the spell management classes --- apps/openmw/mwgui/spellicons.cpp | 194 +++-------------------- apps/openmw/mwgui/spellicons.hpp | 14 +- apps/openmw/mwmechanics/activespells.cpp | 85 +++++++++- apps/openmw/mwmechanics/activespells.hpp | 28 ++-- apps/openmw/mwmechanics/magiceffects.hpp | 13 +- apps/openmw/mwmechanics/spells.cpp | 23 +++ apps/openmw/mwmechanics/spells.hpp | 4 + apps/openmw/mwmechanics/spellsuccess.hpp | 2 +- apps/openmw/mwrender/npcanimation.cpp | 4 +- apps/openmw/mwworld/inventorystore.cpp | 98 +++++++++--- apps/openmw/mwworld/inventorystore.hpp | 18 ++- 11 files changed, 273 insertions(+), 210 deletions(-) diff --git a/apps/openmw/mwgui/spellicons.cpp b/apps/openmw/mwgui/spellicons.cpp index da51f7e8f1..a18e88f5fd 100644 --- a/apps/openmw/mwgui/spellicons.cpp +++ b/apps/openmw/mwgui/spellicons.cpp @@ -21,6 +21,20 @@ namespace MWGui { + void EffectSourceVisitor::visit (const ESM::ENAMstruct& enam, + const std::string& sourceName, float magnitude, float remainingTime) + { + MagicEffectInfo newEffectSource; + newEffectSource.mKey = MWMechanics::EffectKey(enam); + newEffectSource.mMagnitude = magnitude; + newEffectSource.mPermanent = mIsPermanent; + newEffectSource.mRemainingTime = remainingTime; + newEffectSource.mSource = sourceName; + + mEffectSources[enam.mEffectID].push_back(newEffectSource); + } + + void SpellIcons::updateWidgets(MyGUI::Widget *parent, bool adjustSize) { // TODO: Tracking add/remove/expire would be better than force updating every frame @@ -28,125 +42,20 @@ namespace MWGui MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); const MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); - std::map > effects; - // add permanent item enchantments + EffectSourceVisitor visitor; + + // permanent item enchantments & permanent spells + visitor.mIsPermanent = true; MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player); - for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) - { - MWWorld::ContainerStoreIterator it = store.getSlot(slot); - if (it == store.end()) - continue; - std::string enchantment = MWWorld::Class::get(*it).getEnchantment(*it); - if (enchantment.empty()) - continue; - const ESM::Enchantment* enchant = MWBase::Environment::get().getWorld()->getStore().get().find(enchantment); - if (enchant->mData.mType != ESM::Enchantment::ConstantEffect) - continue; + store.visitEffectSources(visitor); + stats.getSpells().visitEffectSources(visitor); - const ESM::EffectList& list = enchant->mEffects; - for (std::vector::const_iterator effectIt = list.mList.begin(); - effectIt != list.mList.end(); ++effectIt) - { - const ESM::MagicEffect* magicEffect = - MWBase::Environment::get().getWorld ()->getStore ().get().find(effectIt->mEffectID); + // now add lasting effects + visitor.mIsPermanent = false; + stats.getActiveSpells().visitEffectSources(visitor); - MagicEffectInfo effectInfo; - effectInfo.mSource = MWWorld::Class::get(*it).getName(*it); - effectInfo.mKey = MWMechanics::EffectKey (effectIt->mEffectID); - if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill) - effectInfo.mKey.mArg = effectIt->mSkill; - else if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute) - effectInfo.mKey.mArg = effectIt->mAttribute; - // just using the min magnitude here, permanent enchantments with a random magnitude just wouldn't make any sense - effectInfo.mMagnitude = effectIt->mMagnMin; - effectInfo.mPermanent = true; - effects[effectIt->mEffectID].push_back (effectInfo); - } - } - - // add permanent spells - const MWMechanics::Spells& spells = stats.getSpells(); - for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it) - { - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(it->first); - - // these are the spell types that are permanently in effect - if (!(spell->mData.mType == ESM::Spell::ST_Ability) - && !(spell->mData.mType == ESM::Spell::ST_Disease) - && !(spell->mData.mType == ESM::Spell::ST_Curse) - && !(spell->mData.mType == ESM::Spell::ST_Blight)) - continue; - const ESM::EffectList& list = spell->mEffects; - for (std::vector::const_iterator effectIt = list.mList.begin(); - effectIt != list.mList.end(); ++effectIt) - { - const ESM::MagicEffect* magicEffect = - MWBase::Environment::get().getWorld ()->getStore ().get().find(effectIt->mEffectID); - MagicEffectInfo effectInfo; - effectInfo.mSource = getSpellDisplayName (it->first); - effectInfo.mKey = MWMechanics::EffectKey (effectIt->mEffectID); - if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill) - effectInfo.mKey.mArg = effectIt->mSkill; - else if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute) - effectInfo.mKey.mArg = effectIt->mAttribute; - // just using the min magnitude here, permanent spells with a random magnitude just wouldn't make any sense - effectInfo.mMagnitude = effectIt->mMagnMin; - effectInfo.mPermanent = true; - - effects[effectIt->mEffectID].push_back (effectInfo); - } - } - - // add lasting effect spells/potions etc - - // TODO: Move this to ActiveSpells - const MWMechanics::ActiveSpells::TContainer& activeSpells = stats.getActiveSpells().getActiveSpells(); - for (MWMechanics::ActiveSpells::TContainer::const_iterator it = activeSpells.begin(); - it != activeSpells.end(); ++it) - { - const ESM::EffectList& list = getSpellEffectList(it->first); - - float timeScale = MWBase::Environment::get().getWorld()->getTimeScaleFactor(); - - int i=0; - for (std::vector::const_iterator effectIt = list.mList.begin(); - effectIt != list.mList.end(); ++effectIt, ++i) - { - if (effectIt->mRange != it->second.mRange) - continue; - - float randomFactor = it->second.mRandom[i]; - - const ESM::MagicEffect* magicEffect = - MWBase::Environment::get().getWorld ()->getStore ().get().find(effectIt->mEffectID); - - MagicEffectInfo effectInfo; - if (!it->second.mName.empty()) - effectInfo.mSource = it->second.mName; - else - effectInfo.mSource = getSpellDisplayName(it->first); - effectInfo.mKey = MWMechanics::EffectKey (effectIt->mEffectID); - if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill) - effectInfo.mKey.mArg = effectIt->mSkill; - else if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute) - effectInfo.mKey.mArg = effectIt->mAttribute; - effectInfo.mMagnitude = effectIt->mMagnMin + (effectIt->mMagnMax-effectIt->mMagnMin) * randomFactor; - effectInfo.mRemainingTime = effectIt->mDuration + - (it->second.mTimeStamp - MWBase::Environment::get().getWorld()->getTimeStamp())*3600/timeScale; - - // ingredients need special casing for their magnitude / duration - if (MWBase::Environment::get().getWorld()->getStore().get().search (it->first)) - { - effectInfo.mRemainingTime = effectIt->mDuration * randomFactor + - (it->second.mTimeStamp - MWBase::Environment::get().getWorld()->getTimeStamp())*3600/timeScale; - - effectInfo.mMagnitude = static_cast (0.05*randomFactor / (0.1 * magicEffect->mData.mBaseCost)); - } - - effects[effectIt->mEffectID].push_back (effectInfo); - } - } + std::map >& effects = visitor.mEffectSources; int w=2; @@ -280,59 +189,4 @@ namespace MWGui } } - - std::string SpellIcons::getSpellDisplayName (const std::string& id) - { - if (const ESM::Spell *spell = - MWBase::Environment::get().getWorld()->getStore().get().search (id)) - return spell->mName; - - if (const ESM::Potion *potion = - MWBase::Environment::get().getWorld()->getStore().get().search (id)) - return potion->mName; - - if (const ESM::Ingredient *ingredient = - MWBase::Environment::get().getWorld()->getStore().get().search (id)) - return ingredient->mName; - - throw std::runtime_error ("ID " + id + " has no display name"); - } - - ESM::EffectList SpellIcons::getSpellEffectList (const std::string& id) - { - if (const ESM::Enchantment* enchantment = - MWBase::Environment::get().getWorld()->getStore().get().search (id)) - return enchantment->mEffects; - - if (const ESM::Spell *spell = - MWBase::Environment::get().getWorld()->getStore().get().search (id)) - return spell->mEffects; - - if (const ESM::Potion *potion = - MWBase::Environment::get().getWorld()->getStore().get().search (id)) - return potion->mEffects; - - if (const ESM::Ingredient *ingredient = - MWBase::Environment::get().getWorld()->getStore().get().search (id)) - { - const ESM::MagicEffect *magicEffect = - MWBase::Environment::get().getWorld()->getStore().get().find ( - ingredient->mData.mEffectID[0]); - - ESM::ENAMstruct effect; - effect.mEffectID = ingredient->mData.mEffectID[0]; - effect.mSkill = ingredient->mData.mSkills[0]; - effect.mAttribute = ingredient->mData.mAttributes[0]; - effect.mRange = 0; - effect.mArea = 0; - effect.mDuration = magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration ? 0 : 1; - effect.mMagnMin = 1; - effect.mMagnMax = 1; - ESM::EffectList result; - result.mList.push_back (effect); - return result; - } - throw std::runtime_error("ID " + id + " does not have effects"); - } - } diff --git a/apps/openmw/mwgui/spellicons.hpp b/apps/openmw/mwgui/spellicons.hpp index 818d67b5bb..bae108a1da 100644 --- a/apps/openmw/mwgui/spellicons.hpp +++ b/apps/openmw/mwgui/spellicons.hpp @@ -2,6 +2,7 @@ #define MWGUI_SPELLICONS_H #include +#include #include "../mwmechanics/magiceffects.hpp" @@ -34,14 +35,23 @@ namespace MWGui bool mPermanent; // the effect is permanent }; + class EffectSourceVisitor : public MWMechanics::EffectSourceVisitor + { + public: + bool mIsPermanent; + + std::map > mEffectSources; + + virtual void visit (const ESM::ENAMstruct& enam, + const std::string& sourceName, float magnitude, float remainingTime = -1); + }; + class SpellIcons { public: void updateWidgets(MyGUI::Widget* parent, bool adjustSize); private: - std::string getSpellDisplayName (const std::string& id); - ESM::EffectList getSpellEffectList (const std::string& id); std::map mWidgetMap; }; diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index 88fa57a8f6..d96a5f3510 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -164,12 +164,31 @@ namespace MWMechanics throw std::runtime_error ("ID " + id + " can not produce lasting effects"); } + std::string ActiveSpells::getSpellDisplayName (const std::string& id) const + { + if (const ESM::Spell *spell = + MWBase::Environment::get().getWorld()->getStore().get().search (id)) + return spell->mName; + + if (const ESM::Potion *potion = + MWBase::Environment::get().getWorld()->getStore().get().search (id)) + return potion->mName; + + if (const ESM::Ingredient *ingredient = + MWBase::Environment::get().getWorld()->getStore().get().search (id)) + return ingredient->mName; + + throw std::runtime_error ("ID " + id + " has no display name"); + } + ActiveSpells::ActiveSpells() : mSpellsChanged (false), mLastUpdate (MWBase::Environment::get().getWorld()->getTimeStamp()) {} - bool ActiveSpells::addSpell (const std::string& id, const MWWorld::Ptr& actor, ESM::RangeType range, const std::string& name) + bool ActiveSpells::addSpell (const std::string& id, const MWWorld::Ptr& actor, ESM::RangeType range, const std::string& name, int effectIndex) { + const CreatureStats& creatureStats = MWWorld::Class::get (actor).getCreatureStats (actor); + std::pair > effects = getEffectList (id); bool stacks = effects.second.second; @@ -195,7 +214,6 @@ namespace MWMechanics if (effects.second.first) { // ingredient -> special treatment required. - const CreatureStats& creatureStats = MWWorld::Class::get (actor).getCreatureStats (actor); const NpcStats& npcStats = MWWorld::Class::get (actor).getNpcStats (actor); float x = @@ -220,6 +238,26 @@ namespace MWMechanics else iter->second = params; + + + /* + for (int i=0; imRange != range) + { + params.mDisabled.push_back(true); + continue; + } + + bool disabled = false; + + int reflect = creatureStats.getMagicEffects().get(ESM::MagicEffect::Reflect).mMagnitude; + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + if (roll < reflect) + disabled = true; + } + */ + // Play sounds & particles bool first=true; for (std::vector::const_iterator iter (effects.first.mList.begin()); @@ -338,4 +376,47 @@ namespace MWMechanics { return mSpells; } + + void ActiveSpells::visitEffectSources(EffectSourceVisitor &visitor) const + { + for (TContainer::const_iterator it = begin(); it != end(); ++it) + { + const ESM::EffectList& list = getEffectList(it->first).first; + + float timeScale = MWBase::Environment::get().getWorld()->getTimeScaleFactor(); + + int i=0; + for (std::vector::const_iterator effectIt = list.mList.begin(); + effectIt != list.mList.end(); ++effectIt, ++i) + { + if (effectIt->mRange != it->second.mRange) + continue; + + std::string name; + if (it->second.mName.empty()) + name = getSpellDisplayName(it->first); + else + name = it->second.mName; + + float remainingTime = effectIt->mDuration + + (it->second.mTimeStamp - MWBase::Environment::get().getWorld()->getTimeStamp())*3600/timeScale; + float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * it->second.mRandom[i]; + + // hack for ingredients + if (MWBase::Environment::get().getWorld()->getStore().get().search (it->first)) + { + const ESM::MagicEffect *magicEffect = + MWBase::Environment::get().getWorld()->getStore().get().find ( + effectIt->mEffectID); + + remainingTime = effectIt->mDuration * it->second.mRandom[i] + + (it->second.mTimeStamp - MWBase::Environment::get().getWorld()->getTimeStamp())*3600/timeScale; + + magnitude = static_cast (0.05*it->second.mRandom[i] / (0.1 * magicEffect->mData.mBaseCost)); + } + + visitor.visit(*effectIt, name, magnitude, remainingTime); + } + } + } } diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index e3f882b9aa..2cf30da94b 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -35,6 +35,10 @@ namespace MWMechanics // Random factor for each effect std::vector mRandom; + // Effect magnitude multiplier. Use 0 to completely disable the effect + // (if it was resisted, reflected or absorbed). Use (0,1) for partially resisted. + std::vector mMultiplier; + // Display name, we need this for enchantments, which don't have a name - so you need to supply the // name of the item with the enchantment to addSpell std::string mName; @@ -65,17 +69,30 @@ namespace MWMechanics std::pair > getEffectList (const std::string& id) const; ///< @return (EffectList, (isIngredient, stacks)) + double timeToExpire (const TIterator& iterator) const; + ///< Returns time (in in-game hours) until the spell pointed to by \a iterator + /// expires. + + const TContainer& getActiveSpells() const; + + TIterator begin() const; + + TIterator end() const; + + std::string getSpellDisplayName (const std::string& id) const; + public: ActiveSpells(); - bool addSpell (const std::string& id, const MWWorld::Ptr& actor, ESM::RangeType range = ESM::RT_Self, const std::string& name = ""); + bool addSpell (const std::string& id, const MWWorld::Ptr& actor, ESM::RangeType range = ESM::RT_Self, const std::string& name = "", int effectIndex = -1); ///< Overwrites an existing spell with the same ID. If the spell does not have any /// non-instant effects, it is ignored. /// @param id /// @param actor /// @param range Only effects with range type \a range will be applied /// @param name Display name for enchantments, since they don't have a name in their record + /// @param effectIndex Only apply one specific effect - useful for reflecting spells, since each effect is reflected individually /// /// \return Has the spell been added? @@ -86,15 +103,8 @@ namespace MWMechanics const MagicEffects& getMagicEffects() const; - const TContainer& getActiveSpells() const; + void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor) const; - TIterator begin() const; - - TIterator end() const; - - double timeToExpire (const TIterator& iterator) const; - ///< Returns time (in in-game hours) until the spell pointed to by \a iterator - /// expires. }; } diff --git a/apps/openmw/mwmechanics/magiceffects.hpp b/apps/openmw/mwmechanics/magiceffects.hpp index 212ef312dd..9cdc27514e 100644 --- a/apps/openmw/mwmechanics/magiceffects.hpp +++ b/apps/openmw/mwmechanics/magiceffects.hpp @@ -2,6 +2,7 @@ #define GAME_MWMECHANICS_MAGICEFFECTS_H #include +#include namespace ESM { @@ -11,6 +12,13 @@ namespace ESM namespace MWMechanics { + // Used by effect management classes (ActiveSpells, InventoryStore, Spells) to list active effect sources for GUI display + struct EffectSourceVisitor + { + virtual void visit (const ESM::ENAMstruct& enam, + const std::string& sourceName, float magnitude, float remainingTime = -1) = 0; + }; + struct EffectKey { int mId; @@ -29,11 +37,12 @@ namespace MWMechanics struct EffectParam { - int mMagnitude; + // Note usually this would be int, but applying partial resistance might introduce decimal point. + float mMagnitude; EffectParam(); - EffectParam(int magnitude) : mMagnitude(magnitude) {} + EffectParam(float magnitude) : mMagnitude(magnitude) {} EffectParam& operator+= (const EffectParam& param); diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index df1f6a3182..a5a5677d12 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -117,4 +117,27 @@ namespace MWMechanics return false; } + + void Spells::visitEffectSources(EffectSourceVisitor &visitor) const + { + for (TIterator it = begin(); it != end(); ++it) + { + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(it->first); + + // these are the spell types that are permanently in effect + if (!(spell->mData.mType == ESM::Spell::ST_Ability) + && !(spell->mData.mType == ESM::Spell::ST_Disease) + && !(spell->mData.mType == ESM::Spell::ST_Curse) + && !(spell->mData.mType == ESM::Spell::ST_Blight)) + continue; + const ESM::EffectList& list = spell->mEffects; + int i=0; + for (std::vector::const_iterator effectIt = list.mList.begin(); + effectIt != list.mList.end(); ++effectIt, ++i) + { + float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * it->second[i]; + visitor.visit(*effectIt, spell->mName, magnitude); + } + } + } } diff --git a/apps/openmw/mwmechanics/spells.hpp b/apps/openmw/mwmechanics/spells.hpp index ccac96190b..79b7a782d0 100644 --- a/apps/openmw/mwmechanics/spells.hpp +++ b/apps/openmw/mwmechanics/spells.hpp @@ -6,6 +6,8 @@ #include "../mwworld/ptr.hpp" +#include "magiceffects.hpp" + namespace ESM { struct Spell; @@ -59,6 +61,8 @@ namespace MWMechanics bool hasCommonDisease() const; bool hasBlightDisease() const; + + void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor) const; }; } diff --git a/apps/openmw/mwmechanics/spellsuccess.hpp b/apps/openmw/mwmechanics/spellsuccess.hpp index 68b89752f8..fc6af3c552 100644 --- a/apps/openmw/mwmechanics/spellsuccess.hpp +++ b/apps/openmw/mwmechanics/spellsuccess.hpp @@ -63,7 +63,7 @@ namespace MWMechanics x *= 0.1 * magicEffect->mData.mBaseCost; x *= 0.5 * (it->mMagnMin + it->mMagnMax); x *= it->mArea * 0.05 * magicEffect->mData.mBaseCost; - if (it->mRange == ESM::RT_Target) + if (magicEffect->mData.mFlags & ESM::MagicEffect::CastTarget) x *= 1.5; static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore().get().find( "fEffectCostMult")->getFloat(); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 6bc2bfc12a..bc7e48af1b 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -60,7 +60,7 @@ const NpcAnimation::PartBoneMap NpcAnimation::sPartList = createPartListMap(); NpcAnimation::~NpcAnimation() { if (!mListenerDisabled) - mPtr.getClass().getInventoryStore(mPtr).setListener(NULL); + mPtr.getClass().getInventoryStore(mPtr).setListener(NULL, mPtr); Ogre::SceneManager *sceneMgr = mInsert->getCreator(); for(size_t i = 0;i < ESM::PRT_Count;i++) @@ -85,7 +85,7 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, int v } if (!disableListener) - mPtr.getClass().getInventoryStore(mPtr).setListener(this); + mPtr.getClass().getInventoryStore(mPtr).setListener(this, mPtr); updateNpcBase(); } diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index c2b1f084d5..bec0593891 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -123,7 +123,7 @@ void MWWorld::InventoryStore::equip (int slot, const ContainerStoreIterator& ite flagAsModified(); fireEquipmentChangedEvent(); - updateMagicEffects(); + updateMagicEffects(actor); } void MWWorld::InventoryStore::unequipAll(const MWWorld::Ptr& actor) @@ -150,10 +150,10 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getSlot (int slot) return mSlots[slot]; } -void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& npc) +void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor) { - const MWMechanics::NpcStats& stats = MWWorld::Class::get(npc).getNpcStats(npc); - MWWorld::InventoryStore& invStore = MWWorld::Class::get(npc).getInventoryStore(npc); + const MWMechanics::NpcStats& stats = MWWorld::Class::get(actor).getNpcStats(actor); + MWWorld::InventoryStore& invStore = MWWorld::Class::get(actor).getInventoryStore(actor); TSlots slots_; initSlots (slots_); @@ -211,15 +211,15 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& npc) } } - switch(MWWorld::Class::get (test).canBeEquipped (test, npc).first) + switch(MWWorld::Class::get (test).canBeEquipped (test, actor).first) { case 0: continue; case 2: - invStore.unequipSlot(MWWorld::InventoryStore::Slot_CarriedLeft, npc); + invStore.unequipSlot(MWWorld::InventoryStore::Slot_CarriedLeft, actor); break; case 3: - invStore.unequipSlot(MWWorld::InventoryStore::Slot_CarriedRight, npc); + invStore.unequipSlot(MWWorld::InventoryStore::Slot_CarriedRight, actor); break; } @@ -255,7 +255,7 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& npc) { mSlots.swap (slots_); fireEquipmentChangedEvent(); - updateMagicEffects(); + updateMagicEffects(actor); flagAsModified(); } } @@ -265,7 +265,7 @@ const MWMechanics::MagicEffects& MWWorld::InventoryStore::getMagicEffects() cons return mMagicEffects; } -void MWWorld::InventoryStore::updateMagicEffects() +void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) { // To avoid excessive updates during auto-equip if (!mUpdatesEnabled) @@ -293,19 +293,41 @@ void MWWorld::InventoryStore::updateMagicEffects() continue; // Roll some dice, one for each effect - std::vector random; - random.resize(enchantment.mEffects.mList.size()); - for (unsigned int i=0; i (std::rand()) / RAND_MAX; + std::vector params; + params.resize(enchantment.mEffects.mList.size()); + for (unsigned int i=0; i (std::rand()) / RAND_MAX; bool existed = (mPermanentMagicEffectMagnitudes.find((**iter).getCellRef().mRefID) != mPermanentMagicEffectMagnitudes.end()); if (!existed) { + // Try resisting each effect + int i=0; + for (std::vector::const_iterator effectIt (enchantment.mEffects.mList.begin()); + effectIt!=enchantment.mEffects.mList.end(); ++effectIt) + { + const ESM::MagicEffect *magicEffect = + MWBase::Environment::get().getWorld()->getStore().get().find ( + effectIt->mEffectID); + + //const MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); + + float resisted = 0; + if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful) + { + + } + params[i].mMultiplier = (100.f - resisted) / 100.f; + + ++i; + } + + // Note that using the RefID as a key here is not entirely correct. // Consider equipping the same item twice (e.g. a ring) // However, permanent enchantments with a random magnitude are kind of an exploit anyway, // so it doesn't really matter if both items will get the same magnitude. *Extreme* edge case. - mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID] = random; + mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID] = params; } int i=0; @@ -316,6 +338,10 @@ void MWWorld::InventoryStore::updateMagicEffects() MWBase::Environment::get().getWorld()->getStore().get().find ( effectIt->mEffectID); + // Fully resisted? + if (params[i].mMultiplier == 0) + continue; + if (!existed) { // During first auto equip, we don't play any sounds. @@ -325,7 +351,10 @@ void MWWorld::InventoryStore::updateMagicEffects() !mFirstAutoEquip && effectIt == enchantment.mEffects.mList.begin()); } - mMagicEffects.add (*effectIt, random[i]); + float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params[i].mRandom; + magnitude *= params[i].mMultiplier; + if (magnitude) + mMagicEffects.add (*effectIt, magnitude); ++i; } } @@ -467,7 +496,7 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, c } fireEquipmentChangedEvent(); - updateMagicEffects(); + updateMagicEffects(actor); return retval; } @@ -487,10 +516,10 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipItem(const MWWor throw std::runtime_error ("attempt to unequip an item that is not currently equipped"); } -void MWWorld::InventoryStore::setListener(InventoryStoreListener *listener) +void MWWorld::InventoryStore::setListener(InventoryStoreListener *listener, const Ptr& actor) { mListener = listener; - updateMagicEffects(); + updateMagicEffects(actor); } void MWWorld::InventoryStore::fireEquipmentChangedEvent() @@ -500,3 +529,36 @@ void MWWorld::InventoryStore::fireEquipmentChangedEvent() if (mListener) mListener->equipmentChanged(); } + +void MWWorld::InventoryStore::visitEffectSources(MWMechanics::EffectSourceVisitor &visitor) +{ + for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter) + { + if (*iter==end()) + continue; + + std::string enchantmentId = MWWorld::Class::get (**iter).getEnchantment (**iter); + if (enchantmentId.empty()) + continue; + + const ESM::Enchantment& enchantment = + *MWBase::Environment::get().getWorld()->getStore().get().find (enchantmentId); + + if (enchantment.mData.mType != ESM::Enchantment::ConstantEffect) + continue; + + if (mPermanentMagicEffectMagnitudes.find((**iter).getCellRef().mRefID) == mPermanentMagicEffectMagnitudes.end()) + continue; + + int i=0; + for (std::vector::const_iterator effectIt (enchantment.mEffects.mList.begin()); + effectIt!=enchantment.mEffects.mList.end(); ++effectIt) + { + float random = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID][i].mRandom; + float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * random; + visitor.visit(*effectIt, (**iter).getClass().getName(**iter), magnitude); + + ++i; + } + } +} diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index 099523a9c8..f53ce8efb9 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -74,7 +74,15 @@ namespace MWWorld // Vanilla allows permanent effects with a random magnitude, so it needs to be stored here. // We also need this to only play sounds and particle effects when the item is equipped, rather than on every update. - typedef std::map > TEffectMagnitudes; + struct EffectParams + { + // Modifier to scale between min and max magnitude + float mRandom; + // Multiplier for when an effect was fully or partially resisted + float mMultiplier; + }; + + typedef std::map > TEffectMagnitudes; TEffectMagnitudes mPermanentMagicEffectMagnitudes; typedef std::vector TSlots; @@ -88,7 +96,7 @@ namespace MWWorld void initSlots (TSlots& slots_); - void updateMagicEffects(); + void updateMagicEffects(const Ptr& actor); void fireEquipmentChangedEvent(); @@ -127,7 +135,7 @@ namespace MWWorld void unequipAll(const MWWorld::Ptr& actor); ///< Unequip all currently equipped items. - void autoEquip (const MWWorld::Ptr& npc); + void autoEquip (const MWWorld::Ptr& actor); ///< Auto equip items according to stats and item value. const MWMechanics::MagicEffects& getMagicEffects() const; @@ -160,8 +168,10 @@ namespace MWWorld /// (it can be re-stacked so its count may be different than when it /// was equipped). - void setListener (InventoryStoreListener* listener); + void setListener (InventoryStoreListener* listener, const Ptr& actor); ///< Set a listener for various events, see \a InventoryStoreListener + + void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor); }; } From aa4b2d9504f642d691e26b63a696d199edf0059d Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 16 Nov 2013 01:19:39 +0100 Subject: [PATCH 280/434] Fix uninitialized mSkill/mAttribute for spellmaker spells --- apps/openmw/mwgui/spellcreationdialog.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index dc86fd825f..e0b808b283 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -9,7 +9,7 @@ #include "../mwworld/player.hpp" -#include "../mwmechanics/spellsuccess.hpp" +#include "../mwmechanics/spellcasting.hpp" #include "tooltips.hpp" #include "class.hpp" @@ -89,6 +89,8 @@ namespace MWGui mEffect.mMagnMax = 1; mEffect.mDuration = 1; mEffect.mArea = 0; + mEffect.mSkill = -1; + mEffect.mAttribute = -1; eventEffectAdded(mEffect); onRangeButtonClicked(mRangeButton); From d49b6f19ffef8bd9ed6fe118233285458592b8dc Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 16 Nov 2013 02:11:11 +0100 Subject: [PATCH 281/434] Don't advance acrobatics skill for NPCs --- apps/openmw/mwmechanics/character.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 76bbafb22b..240695a1cb 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -782,7 +782,7 @@ void CharacterController::update(float duration) if(!onground && !flying && !inwater) { - // The player is in the air (either getting up —ascending part of jump— or falling). + // In the air (either getting up —ascending part of jump— or falling). if (world->isSlowFalling(mPtr)) { @@ -817,8 +817,7 @@ void CharacterController::update(float duration) } else if(vec.z > 0.0f && mJumpState == JumpState_None) { - // The player has started a jump. - + // Started a jump. float z = cls.getJump(mPtr); if(vec.x == 0 && vec.y == 0) vec = Ogre::Vector3(0.0f, 0.0f, z); @@ -829,7 +828,8 @@ void CharacterController::update(float duration) } // advance acrobatics - cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 0); + if (mPtr.getRefData().getHandle() == "player") + cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 0); // decrease fatigue const MWWorld::Store &gmst = world->getStore().get(); @@ -843,8 +843,6 @@ void CharacterController::update(float duration) } else if(mJumpState == JumpState_Falling) { - // The player is landing. - forcestateupdate = true; mJumpState = JumpState_Landing; vec.z = 0.0f; @@ -861,7 +859,8 @@ void CharacterController::update(float duration) cls.getCreatureStats(mPtr).setHealth(health); // report acrobatics progression - cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 1); + if (mPtr.getRefData().getHandle() == "player") + cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 1); const float acrobaticsSkill = cls.getNpcStats(mPtr).getSkill(ESM::Skill::Acrobatics).getModified(); if (healthLost > (acrobaticsSkill * fatigueTerm)) From b1a29eb27eafdc2db1bf7ffb416ac42aea25357f Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 16 Nov 2013 02:34:43 +0100 Subject: [PATCH 282/434] Implement Resist & Weakness effects --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwclass/npc.cpp | 6 +- apps/openmw/mwgui/quickkeysmenu.cpp | 2 +- apps/openmw/mwgui/spellwindow.cpp | 2 +- apps/openmw/mwmechanics/activespells.cpp | 104 +++++++++++------- apps/openmw/mwmechanics/activespells.hpp | 7 +- .../{spellsuccess.hpp => spellcasting.hpp} | 66 +++++++++++ apps/openmw/mwworld/actiontrap.cpp | 2 +- apps/openmw/mwworld/inventorystore.cpp | 36 +++--- apps/openmw/mwworld/worldimp.cpp | 6 +- 10 files changed, 159 insertions(+), 74 deletions(-) rename apps/openmw/mwmechanics/{spellsuccess.hpp => spellcasting.hpp} (64%) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 04bd89f959..cc4a9d6de3 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -69,7 +69,7 @@ add_openmw_dir (mwclass add_openmw_dir (mwmechanics mechanicsmanagerimp stat character creaturestats magiceffects movement actors objects drawstate spells activespells npcstats aipackage aisequence alchemy aiwander aitravel aifollow - aiescort aiactivate repair enchanting pathfinding security spellsuccess + aiescort aiactivate repair enchanting pathfinding security spellsuccess spellcasting ) add_openmw_dir (mwbase diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 35a6d17a3b..fd24dd93fc 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -468,9 +468,9 @@ namespace MWClass { weapon.getCellRef().mEnchantmentCharge -= castCost; // Touch - othercls.getCreatureStats(victim).getActiveSpells().addSpell(enchantmentName, victim, ESM::RT_Touch, weapon.getClass().getName(weapon)); + othercls.getCreatureStats(victim).getActiveSpells().addSpell(enchantmentName, victim, ptr, ESM::RT_Touch, weapon.getClass().getName(weapon)); // Self - getCreatureStats(ptr).getActiveSpells().addSpell(enchantmentName, ptr, ESM::RT_Self, weapon.getClass().getName(weapon)); + getCreatureStats(ptr).getActiveSpells().addSpell(enchantmentName, ptr, ptr, ESM::RT_Self, weapon.getClass().getName(weapon)); // Target MWBase::Environment::get().getWorld()->launchProjectile(enchantmentName, enchantment->mEffects, ptr, weapon.getClass().getName(weapon)); } @@ -936,7 +936,7 @@ namespace MWClass /// \todo consider instant effects - return stats.getActiveSpells().addSpell (id, actor); + return stats.getActiveSpells().addSpell (id, actor, actor); } void Npc::skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType) const diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 5f749d3d3d..503bf7c114 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -5,7 +5,7 @@ #include "../mwworld/player.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/actionequip.hpp" -#include "../mwmechanics/spellsuccess.hpp" +#include "../mwmechanics/spellcasting.hpp" #include "../mwgui/inventorywindow.hpp" #include "../mwgui/bookwindow.hpp" #include "../mwgui/scrollwindow.hpp" diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp index 61ac2c7b29..03bb106310 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -9,7 +9,7 @@ #include "../mwworld/inventorystore.hpp" #include "../mwworld/actionequip.hpp" -#include "../mwmechanics/spellsuccess.hpp" +#include "../mwmechanics/spellcasting.hpp" #include "spellicons.hpp" #include "inventorywindow.hpp" diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index d96a5f3510..3a9321f398 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -16,11 +16,14 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwbase/windowmanager.hpp" #include "../mwrender/animation.hpp" #include "../mwworld/class.hpp" +#include "../mwmechanics/spellcasting.hpp" + #include "creaturestats.hpp" #include "npcstats.hpp" @@ -74,7 +77,7 @@ namespace MWMechanics for (std::vector::const_iterator effectIter (effects.first.mList.begin()); effectIter!=effects.first.mList.end(); ++effectIter, ++i) { - float magnitude = iter->second.mRandom[i]; + float random = iter->second.mRandom[i]; if (effectIter->mRange != iter->second.mRange) continue; @@ -83,7 +86,7 @@ namespace MWMechanics int duration = effectIter->mDuration; if (effects.second.first) - duration *= magnitude; + duration *= random; MWWorld::TimeStamp end = start; end += static_cast (duration)* @@ -102,19 +105,21 @@ namespace MWMechanics if (effectIter->mDuration==0) { param.mMagnitude = - static_cast (magnitude / (0.1 * magicEffect->mData.mBaseCost)); + static_cast (random / (0.1 * magicEffect->mData.mBaseCost)); } else { param.mMagnitude = - static_cast (0.05*magnitude / (0.1 * magicEffect->mData.mBaseCost)); + static_cast (0.05*random / (0.1 * magicEffect->mData.mBaseCost)); } } else param.mMagnitude = static_cast ( - (effectIter->mMagnMax-effectIter->mMagnMin)*magnitude + effectIter->mMagnMin); - - mEffects.add (*effectIter, param); + (effectIter->mMagnMax-effectIter->mMagnMin)*random + effectIter->mMagnMin); + param.mMagnitude *= iter->second.mMultiplier[i]; + + if (param.mMagnitude) + mEffects.add (*effectIter, param); } } } @@ -185,7 +190,7 @@ namespace MWMechanics : mSpellsChanged (false), mLastUpdate (MWBase::Environment::get().getWorld()->getTimeStamp()) {} - bool ActiveSpells::addSpell (const std::string& id, const MWWorld::Ptr& actor, ESM::RangeType range, const std::string& name, int effectIndex) + bool ActiveSpells::addSpell (const std::string& id, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, ESM::RangeType range, const std::string& name, int effectIndex) { const CreatureStats& creatureStats = MWWorld::Class::get (actor).getCreatureStats (actor); @@ -197,6 +202,8 @@ namespace MWMechanics for (std::vector::const_iterator iter (effects.first.mList.begin()); iter!=effects.first.mList.end(); ++iter) { + if (iter->mRange != range) + continue; if (iter->mDuration) { found = true; @@ -204,41 +211,37 @@ namespace MWMechanics } } + // If none of the effects need to apply, no need to add the spell if (!found) return false; TContainer::iterator iter = mSpells.find (id); - float random = static_cast (std::rand()) / RAND_MAX; - - if (effects.second.first) - { - // ingredient -> special treatment required. - const NpcStats& npcStats = MWWorld::Class::get (actor).getNpcStats (actor); - - float x = - (npcStats.getSkill (ESM::Skill::Alchemy).getModified() + - 0.2 * creatureStats.getAttribute (1).getModified() - + 0.1 * creatureStats.getAttribute (7).getModified()) - * creatureStats.getFatigueTerm(); - random *= 100; - random = random / std::min (x, 100.0f); - random *= 0.25 * x; - } - ActiveSpellParams params; for (unsigned int i=0; i (std::rand()) / RAND_MAX); + { + float random = static_cast (std::rand()) / RAND_MAX; + if (effects.second.first) + { + // ingredient -> special treatment required. + const NpcStats& npcStats = MWWorld::Class::get (actor).getNpcStats (actor); + + float x = + (npcStats.getSkill (ESM::Skill::Alchemy).getModified() + + 0.2 * creatureStats.getAttribute (1).getModified() + + 0.1 * creatureStats.getAttribute (7).getModified()) + * creatureStats.getFatigueTerm(); + random *= 100; + random = random / std::min (x, 100.0f); + random *= 0.25 * x; + } + + params.mRandom.push_back(random); + } params.mRange = range; params.mTimeStamp = MWBase::Environment::get().getWorld()->getTimeStamp(); params.mName = name; - - if (iter==mSpells.end() || stacks) - mSpells.insert (std::make_pair (id, params)); - else - iter->second = params; - - + params.mMultiplier.resize(effects.first.mList.size(), 1); /* for (int i=0; i::const_iterator iter (effects.first.mList.begin()); - iter!=effects.first.mList.end(); ++iter) + int i = 0; + for (std::vector::const_iterator effectIt (effects.first.mList.begin()); + effectIt!=effects.first.mList.end(); ++effectIt, ++i) { - if (iter->mRange != range) + if (effectIt->mRange != range) + continue; + + // Try resisting effect in case its harmful + const ESM::Spell *spell = + MWBase::Environment::get().getWorld()->getStore().get().search (id); + params.mMultiplier[i] = MWMechanics::getEffectMultiplier(effectIt->mEffectID, actor, caster, spell); + if (params.mMultiplier[i] == 0) + { + if (actor.getRefData().getHandle() == "player") + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); + else + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}"); + } + + // If fully resisted, don't play sounds or particles + if (params.mMultiplier[i] == 0) continue; // TODO: For Area effects, launch a growing particle effect that applies the effect to more actors as it hits them. Best managed in World. const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find ( - iter->mEffectID); + effectIt->mEffectID); // Only the sound of the first effect plays if (first) @@ -296,6 +315,11 @@ namespace MWMechanics first = false; } + if (iter==mSpells.end() || stacks) + mSpells.insert (std::make_pair (id, params)); + else + iter->second = params; + mSpellsChanged = true; return true; @@ -415,7 +439,9 @@ namespace MWMechanics magnitude = static_cast (0.05*it->second.mRandom[i] / (0.1 * magicEffect->mData.mBaseCost)); } - visitor.visit(*effectIt, name, magnitude, remainingTime); + magnitude *= it->second.mMultiplier[i]; + if (magnitude) + visitor.visit(*effectIt, name, magnitude, remainingTime); } } } diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index 2cf30da94b..b5c302afe1 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -37,7 +37,7 @@ namespace MWMechanics // Effect magnitude multiplier. Use 0 to completely disable the effect // (if it was resisted, reflected or absorbed). Use (0,1) for partially resisted. - std::vector mMultiplier; + std::vector mMultiplier; // Display name, we need this for enchantments, which don't have a name - so you need to supply the // name of the item with the enchantment to addSpell @@ -85,11 +85,12 @@ namespace MWMechanics ActiveSpells(); - bool addSpell (const std::string& id, const MWWorld::Ptr& actor, ESM::RangeType range = ESM::RT_Self, const std::string& name = "", int effectIndex = -1); + bool addSpell (const std::string& id, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, ESM::RangeType range = ESM::RT_Self, const std::string& name = "", int effectIndex = -1); ///< Overwrites an existing spell with the same ID. If the spell does not have any /// non-instant effects, it is ignored. /// @param id - /// @param actor + /// @param actor actor to add the spell to + /// @param caster actor who casted the spell /// @param range Only effects with range type \a range will be applied /// @param name Display name for enchantments, since they don't have a name in their record /// @param effectIndex Only apply one specific effect - useful for reflecting spells, since each effect is reflected individually diff --git a/apps/openmw/mwmechanics/spellsuccess.hpp b/apps/openmw/mwmechanics/spellcasting.hpp similarity index 64% rename from apps/openmw/mwmechanics/spellsuccess.hpp rename to apps/openmw/mwmechanics/spellcasting.hpp index fc6af3c552..3bbb4d0c53 100644 --- a/apps/openmw/mwmechanics/spellsuccess.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -114,6 +114,72 @@ namespace MWMechanics return school; } + /// @return >=100 for fully resisted. can also return negative value for damage amplification. + inline float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell = NULL) + { + const ESM::MagicEffect *magicEffect = + MWBase::Environment::get().getWorld()->getStore().get().find ( + effectId); + + const MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); + + float resisted = 0; + if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful) + { + + short resistanceEffect = ESM::MagicEffect::getResistanceEffect(effectId); + short weaknessEffect = ESM::MagicEffect::getWeaknessEffect(effectId); + + float resistance = 0; + if (resistanceEffect != -1) + resistance += stats.getMagicEffects().get(resistanceEffect).mMagnitude; + if (weaknessEffect != -1) + resistance -= stats.getMagicEffects().get(weaknessEffect).mMagnitude; + + + float willpower = stats.getAttribute(ESM::Attribute::Willpower).getModified(); + float luck = stats.getAttribute(ESM::Attribute::Luck).getModified(); + float x = (willpower + 0.1 * luck) * stats.getFatigueTerm(); + + // This makes spells that are easy to cast harder to resist and vice versa + if (spell != NULL) + { + float castChance = getSpellSuccessChance(spell, caster); + if (castChance > 0) + x *= 50 / castChance; + } + + float roll = static_cast(std::rand()) / RAND_MAX * 100; + if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude) + roll -= resistance; + + if (x <= roll) + x = 0; + else + { + if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude) + x = 100; + else + x = roll / std::min(x, 100.f); + } + + x = std::min(x + resistance, 100.f); + + resisted = x; + } + + return resisted; + } + + inline float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell = NULL) + { + float resistance = getEffectResistance(effectId, actor, caster, spell); + if (resistance >= 0) + return 1 - resistance / 100.f; + else + return -(resistance-100) / 100.f; + } + } #endif diff --git a/apps/openmw/mwworld/actiontrap.cpp b/apps/openmw/mwworld/actiontrap.cpp index 13b2fd2694..80da290727 100644 --- a/apps/openmw/mwworld/actiontrap.cpp +++ b/apps/openmw/mwworld/actiontrap.cpp @@ -16,7 +16,7 @@ namespace MWWorld // TODO: Apply RT_Self effects on the door / container that triggered the trap. Not terribly useful, but you could // make it lock itself when activated for example. - actor.getClass().getCreatureStats(actor).getActiveSpells().addSpell(mSpellId, actor, ESM::RT_Touch); + actor.getClass().getCreatureStats(actor).getActiveSpells().addSpell(mSpellId, actor, actor, ESM::RT_Touch); const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(mSpellId); diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index bec0593891..9e18691254 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -11,6 +11,8 @@ #include "../mwbase/windowmanager.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/spellcasting.hpp" + #include "esmstore.hpp" #include "class.hpp" @@ -292,47 +294,37 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) if (enchantment.mData.mType != ESM::Enchantment::ConstantEffect) continue; - // Roll some dice, one for each effect std::vector params; - params.resize(enchantment.mEffects.mList.size()); - for (unsigned int i=0; i (std::rand()) / RAND_MAX; bool existed = (mPermanentMagicEffectMagnitudes.find((**iter).getCellRef().mRefID) != mPermanentMagicEffectMagnitudes.end()); if (!existed) { + // Roll some dice, one for each effect + params.resize(enchantment.mEffects.mList.size()); + for (unsigned int i=0; i (std::rand()) / RAND_MAX; + // Try resisting each effect int i=0; for (std::vector::const_iterator effectIt (enchantment.mEffects.mList.begin()); effectIt!=enchantment.mEffects.mList.end(); ++effectIt) { - const ESM::MagicEffect *magicEffect = - MWBase::Environment::get().getWorld()->getStore().get().find ( - effectIt->mEffectID); - - //const MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); - - float resisted = 0; - if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful) - { - - } - params[i].mMultiplier = (100.f - resisted) / 100.f; - + params[i].mMultiplier = MWMechanics::getEffectMultiplier(effectIt->mEffectID, actor, actor); ++i; } - // Note that using the RefID as a key here is not entirely correct. // Consider equipping the same item twice (e.g. a ring) // However, permanent enchantments with a random magnitude are kind of an exploit anyway, // so it doesn't really matter if both items will get the same magnitude. *Extreme* edge case. mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID] = params; } + else + params = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID]; int i=0; for (std::vector::const_iterator effectIt (enchantment.mEffects.mList.begin()); - effectIt!=enchantment.mEffects.mList.end(); ++effectIt) + effectIt!=enchantment.mEffects.mList.end(); ++effectIt, ++i) { const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find ( @@ -355,7 +347,6 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) magnitude *= params[i].mMultiplier; if (magnitude) mMagicEffects.add (*effectIt, magnitude); - ++i; } } } @@ -554,8 +545,9 @@ void MWWorld::InventoryStore::visitEffectSources(MWMechanics::EffectSourceVisito for (std::vector::const_iterator effectIt (enchantment.mEffects.mList.begin()); effectIt!=enchantment.mEffects.mList.end(); ++effectIt) { - float random = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID][i].mRandom; - float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * random; + const EffectParams& params = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID][i]; + float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params.mRandom; + magnitude *= params.mMultiplier; visitor.visit(*effectIt, (**iter).getClass().getName(**iter), magnitude); ++i; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index eca2ebb79f..e6c4c1ef08 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -26,7 +26,7 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/movement.hpp" #include "../mwmechanics/npcstats.hpp" -#include "../mwmechanics/spellsuccess.hpp" +#include "../mwmechanics/spellcasting.hpp" #include "../mwrender/sky.hpp" @@ -2158,7 +2158,7 @@ namespace MWWorld // Now apply the spell! // Apply Self portion - actor.getClass().getCreatureStats(actor).getActiveSpells().addSpell(selectedSpell, actor, ESM::RT_Self, sourceName); + actor.getClass().getCreatureStats(actor).getActiveSpells().addSpell(selectedSpell, actor, actor, ESM::RT_Self, sourceName); // Apply Touch portion // TODO: Distance is probably incorrect, and should it be hardcoded? @@ -2166,7 +2166,7 @@ namespace MWWorld if (!contact.first.isEmpty()) { if (contact.first.getClass().isActor()) - contact.first.getClass().getCreatureStats(contact.first).getActiveSpells().addSpell(selectedSpell, contact.first, ESM::RT_Touch, sourceName); + contact.first.getClass().getCreatureStats(contact.first).getActiveSpells().addSpell(selectedSpell, contact.first, actor, ESM::RT_Touch, sourceName); else { // We hit a non-actor, e.g. a door. Only instant effects are relevant. From bb43ec9b35b3f45d55de492834b5d45f550890f5 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 16 Nov 2013 03:16:21 +0100 Subject: [PATCH 283/434] Implement damage tick effects --- apps/openmw/mwmechanics/activespells.cpp | 12 ++++++++---- apps/openmw/mwmechanics/actors.cpp | 18 ++++++++++++++++-- apps/openmw/mwmechanics/actors.hpp | 2 +- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index 3a9321f398..89543476b0 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -269,6 +269,14 @@ namespace MWMechanics if (effectIt->mRange != range) continue; + const ESM::MagicEffect *magicEffect = + MWBase::Environment::get().getWorld()->getStore().get().find ( + effectIt->mEffectID); + + if (caster.getRefData().getHandle() == "player" && actor != caster + && magicEffect->mData.mFlags & ESM::MagicEffect::Harmful) + MWBase::Environment::get().getWindowManager()->setEnemy(actor); + // Try resisting effect in case its harmful const ESM::Spell *spell = MWBase::Environment::get().getWorld()->getStore().get().search (id); @@ -287,10 +295,6 @@ namespace MWMechanics // TODO: For Area effects, launch a growing particle effect that applies the effect to more actors as it hits them. Best managed in World. - const ESM::MagicEffect *magicEffect = - MWBase::Environment::get().getWorld()->getStore().get().find ( - effectIt->mEffectID); - // Only the sound of the first effect plays if (first) { diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 3d52ce8e69..576c830da7 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -29,7 +29,7 @@ namespace MWMechanics // magic effects adjustMagicEffects (ptr); calculateDynamicStats (ptr); - calculateCreatureStatModifiers (ptr); + calculateCreatureStatModifiers (ptr, duration); if(!MWBase::Environment::get().getWindowManager()->isGuiMode()) { @@ -145,7 +145,7 @@ namespace MWMechanics stats.setFatigue (fatigue); } - void Actors::calculateCreatureStatModifiers (const MWWorld::Ptr& ptr) + void Actors::calculateCreatureStatModifiers (const MWWorld::Ptr& ptr, float duration) { CreatureStats &creatureStats = MWWorld::Class::get(ptr).getCreatureStats(ptr); const MagicEffects &effects = creatureStats.getMagicEffects(); @@ -167,10 +167,24 @@ namespace MWMechanics stat.setModifier(effects.get(EffectKey(ESM::MagicEffect::FortifyHealth+i)).mMagnitude - effects.get(EffectKey(ESM::MagicEffect::DrainHealth+i)).mMagnitude); + float damage = creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::DamageHealth)).mMagnitude; + stat.setCurrent(stat.getCurrent() - damage * duration); + creatureStats.setDynamic(i, stat); } + // Apply damage ticks + int damageEffects[] = { + ESM::MagicEffect::FireDamage, ESM::MagicEffect::ShockDamage, ESM::MagicEffect::FrostDamage, ESM::MagicEffect::Poison + }; + for (unsigned int i=0; i health = creatureStats.getHealth(); + health.setCurrent(health.getCurrent() - magnitude * duration); + creatureStats.setHealth(health); + } } void Actors::calculateNpcStatModifiers (const MWWorld::Ptr& ptr) diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index eeef22635c..251c38ec0e 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -40,7 +40,7 @@ namespace MWMechanics void calculateDynamicStats (const MWWorld::Ptr& ptr); - void calculateCreatureStatModifiers (const MWWorld::Ptr& ptr); + void calculateCreatureStatModifiers (const MWWorld::Ptr& ptr, float duration); void calculateNpcStatModifiers (const MWWorld::Ptr& ptr); void calculateRestoration (const MWWorld::Ptr& ptr, float duration); From 7474e87edce448361d6f48eb4013bac85c7a0c02 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 16 Nov 2013 05:06:54 +0100 Subject: [PATCH 284/434] Implement RestoreHealth/Magicka/Fatigue --- apps/openmw/mwmechanics/activespells.hpp | 2 +- apps/openmw/mwmechanics/actors.cpp | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index b5c302afe1..56d9413c39 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -57,7 +57,7 @@ namespace MWMechanics private: - mutable TContainer mSpells; // spellId, (time of casting, relative magnitude) + mutable TContainer mSpells; mutable MagicEffects mEffects; mutable bool mSpellsChanged; mutable MWWorld::TimeStamp mLastUpdate; diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 576c830da7..2873816f73 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -167,8 +167,9 @@ namespace MWMechanics stat.setModifier(effects.get(EffectKey(ESM::MagicEffect::FortifyHealth+i)).mMagnitude - effects.get(EffectKey(ESM::MagicEffect::DrainHealth+i)).mMagnitude); - float damage = creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::DamageHealth)).mMagnitude; - stat.setCurrent(stat.getCurrent() - damage * duration); + float currentDiff = creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::RestoreHealth+i)).mMagnitude + - creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::DamageHealth+i)).mMagnitude; + stat.setCurrent(stat.getCurrent() + currentDiff * duration); creatureStats.setDynamic(i, stat); } From aa84ce3f0dfacca1b85d7efa93b20a33b76d97e9 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 16 Nov 2013 14:44:31 +0100 Subject: [PATCH 285/434] Copy paste mistake (oops) --- apps/openmw/mwmechanics/magiceffects.hpp | 2 -- components/esm/loadmgef.cpp | 2 -- 2 files changed, 4 deletions(-) diff --git a/apps/openmw/mwmechanics/magiceffects.hpp b/apps/openmw/mwmechanics/magiceffects.hpp index 9cdc27514e..58f023ebac 100644 --- a/apps/openmw/mwmechanics/magiceffects.hpp +++ b/apps/openmw/mwmechanics/magiceffects.hpp @@ -24,8 +24,6 @@ namespace MWMechanics int mId; int mArg; // skill or ability - // TODO: Add caster here for Absorb effects? - EffectKey(); EffectKey (int id, int arg = -1) : mId (id), mArg (arg) {} diff --git a/components/esm/loadmgef.cpp b/components/esm/loadmgef.cpp index 46e2a8555d..f601915395 100644 --- a/components/esm/loadmgef.cpp +++ b/components/esm/loadmgef.cpp @@ -132,8 +132,6 @@ short MagicEffect::getResistanceEffect(short effect) short MagicEffect::getWeaknessEffect(short effect) { std::map effects; - effects[DisintegrateArmor] = Sanctuary; - effects[DisintegrateWeapon] = Sanctuary; for (int i=0; i<5; ++i) effects[DrainAttribute+i] = WeaknessToMagicka; From b9899696e31cd1f4f969effcbb5819b4d14469d5 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 16 Nov 2013 15:56:15 +0100 Subject: [PATCH 286/434] Add a crash catcher for unix. When encountering a fatal signal, attach gdb and log backtrace. --- apps/openmw/CMakeLists.txt | 1 + apps/openmw/crashcatcher.cpp | 462 +++++++++++++++++++++++++++++++++++ apps/openmw/main.cpp | 18 ++ 3 files changed, 481 insertions(+) create mode 100644 apps/openmw/crashcatcher.cpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 04bd89f959..a3f3745a23 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -6,6 +6,7 @@ configure_file ("${CMAKE_CURRENT_SOURCE_DIR}/config.hpp.cmake" "${CMAKE_CURRENT_ set(GAME main.cpp engine.cpp + crashcatcher.cpp ) set(GAME_HEADER engine.hpp diff --git a/apps/openmw/crashcatcher.cpp b/apps/openmw/crashcatcher.cpp new file mode 100644 index 0000000000..2a1abdcec7 --- /dev/null +++ b/apps/openmw/crashcatcher.cpp @@ -0,0 +1,462 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include +#include +#include + +#include + +#include + +#ifdef __linux__ +#include +#ifndef PR_SET_PTRACER +#define PR_SET_PTRACER 0x59616d61 +#endif +#elif defined (__APPLE__) +#include +#endif + + +static const char crash_switch[] = "--cc-handle-crash"; + +static const char fatal_err[] = "\n\n*** Fatal Error ***\n"; +static const char pipe_err[] = "!!! Failed to create pipe\n"; +static const char fork_err[] = "!!! Failed to fork debug process\n"; +static const char exec_err[] = "!!! Failed to exec debug process\n"; + +static char argv0[PATH_MAX]; + +static char altstack[SIGSTKSZ]; + + +static struct { + int signum; + pid_t pid; + int has_siginfo; + siginfo_t siginfo; + char buf[1024]; +} crash_info; + + +static const struct { + const char *name; + int signum; +} signals[] = { + { "Segmentation fault", SIGSEGV }, + { "Illegal instruction", SIGILL }, + { "FPU exception", SIGFPE }, + { "System BUS error", SIGBUS }, + { NULL, 0 } +}; + +static const struct { + int code; + const char *name; +} sigill_codes[] = { +#ifndef __FreeBSD__ + { ILL_ILLOPC, "Illegal opcode" }, + { ILL_ILLOPN, "Illegal operand" }, + { ILL_ILLADR, "Illegal addressing mode" }, + { ILL_ILLTRP, "Illegal trap" }, + { ILL_PRVOPC, "Privileged opcode" }, + { ILL_PRVREG, "Privileged register" }, + { ILL_COPROC, "Coprocessor error" }, + { ILL_BADSTK, "Internal stack error" }, +#endif + { 0, NULL } +}; + +static const struct { + int code; + const char *name; +} sigfpe_codes[] = { + { FPE_INTDIV, "Integer divide by zero" }, + { FPE_INTOVF, "Integer overflow" }, + { FPE_FLTDIV, "Floating point divide by zero" }, + { FPE_FLTOVF, "Floating point overflow" }, + { FPE_FLTUND, "Floating point underflow" }, + { FPE_FLTRES, "Floating point inexact result" }, + { FPE_FLTINV, "Floating point invalid operation" }, + { FPE_FLTSUB, "Subscript out of range" }, + { 0, NULL } +}; + +static const struct { + int code; + const char *name; +} sigsegv_codes[] = { +#ifndef __FreeBSD__ + { SEGV_MAPERR, "Address not mapped to object" }, + { SEGV_ACCERR, "Invalid permissions for mapped object" }, +#endif + { 0, NULL } +}; + +static const struct { + int code; + const char *name; +} sigbus_codes[] = { +#ifndef __FreeBSD__ + { BUS_ADRALN, "Invalid address alignment" }, + { BUS_ADRERR, "Non-existent physical address" }, + { BUS_OBJERR, "Object specific hardware error" }, +#endif + { 0, NULL } +}; + +static int (*cc_user_info)(char*, char*); + + +static void gdb_info(pid_t pid) +{ + char respfile[64]; + char cmd_buf[128]; + FILE *f; + int fd; + + /* Create a temp file to put gdb commands into */ + strcpy(respfile, "gdb-respfile-XXXXXX"); + if((fd=mkstemp(respfile)) >= 0 && (f=fdopen(fd, "w")) != NULL) + { + fprintf(f, "attach %d\n" + "shell echo \"\"\n" + "shell echo \"* Loaded Libraries\"\n" + "info sharedlibrary\n" + "shell echo \"\"\n" + "shell echo \"* Threads\"\n" + "info threads\n" + "shell echo \"\"\n" + "shell echo \"* FPU Status\"\n" + "info float\n" + "shell echo \"\"\n" + "shell echo \"* Registers\"\n" + "info registers\n" + "shell echo \"\"\n" + "shell echo \"* Backtrace\"\n" + "thread apply all backtrace full\n" + "detach\n" + "quit\n", pid); + fclose(f); + + /* Run gdb and print process info. */ + snprintf(cmd_buf, sizeof(cmd_buf), "gdb --quiet --batch --command=%s", respfile); + printf("Executing: %s\n", cmd_buf); + fflush(stdout); + + system(cmd_buf); + /* Clean up */ + remove(respfile); + } + else + { + /* Error creating temp file */ + if(fd >= 0) + { + close(fd); + remove(respfile); + } + printf("!!! Could not create gdb command file\n"); + } + fflush(stdout); +} + +static void sys_info(void) +{ +#ifdef __unix__ + system("echo \"System: `uname -a`\""); + putchar('\n'); + fflush(stdout); +#endif +} + + +static size_t safe_write(int fd, const void *buf, size_t len) +{ + size_t ret = 0; + while(ret < len) + { + ssize_t rem; + if((rem=write(fd, (const char*)buf+ret, len-ret)) == -1) + { + if(errno == EINTR) + continue; + break; + } + ret += rem; + } + return ret; +} + +static void crash_catcher(int signum, siginfo_t *siginfo, void *context) +{ + //ucontext_t *ucontext = (ucontext_t*)context; + pid_t dbg_pid; + int fd[2]; + + /* Make sure the effective uid is the real uid */ + if(getuid() != geteuid()) + { + raise(signum); + return; + } + + safe_write(STDERR_FILENO, fatal_err, sizeof(fatal_err)-1); + if(pipe(fd) == -1) + { + safe_write(STDERR_FILENO, pipe_err, sizeof(pipe_err)-1); + raise(signum); + return; + } + + crash_info.signum = signum; + crash_info.pid = getpid(); + crash_info.has_siginfo = !!siginfo; + if(siginfo) + crash_info.siginfo = *siginfo; + if(cc_user_info) + cc_user_info(crash_info.buf, crash_info.buf+sizeof(crash_info.buf)); + + /* Fork off to start a crash handler */ + switch((dbg_pid=fork())) + { + /* Error */ + case -1: + safe_write(STDERR_FILENO, fork_err, sizeof(fork_err)-1); + raise(signum); + return; + + case 0: + dup2(fd[0], STDIN_FILENO); + close(fd[0]); + close(fd[1]); + + execl(argv0, argv0, crash_switch, NULL); + + safe_write(STDERR_FILENO, exec_err, sizeof(exec_err)-1); + _exit(1); + + default: +#ifdef __linux__ + prctl(PR_SET_PTRACER, dbg_pid, 0, 0, 0); +#endif + safe_write(fd[1], &crash_info, sizeof(crash_info)); + close(fd[0]); + close(fd[1]); + + /* Wait; we'll be killed when gdb is done */ + do { + int status; + if(waitpid(dbg_pid, &status, 0) == dbg_pid && + (WIFEXITED(status) || WIFSIGNALED(status))) + { + /* The debug process died before it could kill us */ + raise(signum); + break; + } + } while(1); + } +} + +static void crash_handler(const char *logfile) +{ + const char *sigdesc = ""; + int i; + + if(fread(&crash_info, sizeof(crash_info), 1, stdin) != 1) + { + fprintf(stderr, "!!! Failed to retrieve info from crashed process\n"); + exit(1); + } + + /* Get the signal description */ + for(i = 0;signals[i].name;++i) + { + if(signals[i].signum == crash_info.signum) + { + sigdesc = signals[i].name; + break; + } + } + + if(crash_info.has_siginfo) + { + switch(crash_info.signum) + { + case SIGSEGV: + for(i = 0;sigsegv_codes[i].name;++i) + { + if(sigsegv_codes[i].code == crash_info.siginfo.si_code) + { + sigdesc = sigsegv_codes[i].name; + break; + } + } + break; + + case SIGFPE: + for(i = 0;sigfpe_codes[i].name;++i) + { + if(sigfpe_codes[i].code == crash_info.siginfo.si_code) + { + sigdesc = sigfpe_codes[i].name; + break; + } + } + break; + + case SIGILL: + for(i = 0;sigill_codes[i].name;++i) + { + if(sigill_codes[i].code == crash_info.siginfo.si_code) + { + sigdesc = sigill_codes[i].name; + break; + } + } + break; + + case SIGBUS: + for(i = 0;sigbus_codes[i].name;++i) + { + if(sigbus_codes[i].code == crash_info.siginfo.si_code) + { + sigdesc = sigbus_codes[i].name; + break; + } + } + break; + } + } + fprintf(stderr, "%s (signal %i)\n", sigdesc, crash_info.signum); + if(crash_info.has_siginfo) + fprintf(stderr, "Address: %p\n", crash_info.siginfo.si_addr); + fputc('\n', stderr); + + if(logfile) + { + /* Create crash log file and redirect shell output to it */ + if(freopen(logfile, "wa", stdout) != stdout) + { + fprintf(stderr, "!!! Could not create %s following signal\n", logfile); + exit(1); + } + fprintf(stderr, "Generating %s and killing process %d, please wait... ", logfile, crash_info.pid); + + printf("*** Fatal Error ***\n" + "%s (signal %i)\n", sigdesc, crash_info.signum); + if(crash_info.has_siginfo) + printf("Address: %p\n", crash_info.siginfo.si_addr); + fputc('\n', stdout); + fflush(stdout); + } + + sys_info(); + + crash_info.buf[sizeof(crash_info.buf)-1] = '\0'; + printf("%s\n", crash_info.buf); + fflush(stdout); + + if(crash_info.pid > 0) + { + gdb_info(crash_info.pid); + kill(crash_info.pid, SIGKILL); + } + + if(logfile) + { + char cwd[MAXPATHLEN]; + getcwd(cwd, MAXPATHLEN); + + std::string message = "OpenMW has encountered a fatal error.\nCrash log saved to '" + std::string(cwd) + "/" + std::string(logfile) + "'.\n Please report this to https://bugs.openmw.org !"; + SDL_ShowSimpleMessageBox(0, "Fatal Error", message.c_str(), NULL); + } + exit(0); +} + +int cc_install_handlers(int argc, char **argv, int num_signals, int *signals, const char *logfile, int (*user_info)(char*, char*)) +{ + struct sigaction sa; + stack_t altss; + int retval; + + if(argc == 2 && strcmp(argv[1], crash_switch) == 0) + crash_handler(logfile); + + cc_user_info = user_info; + + if(argv[0][0] == '/') + snprintf(argv0, sizeof(argv0), "%s", argv[0]); + else + { + getcwd(argv0, sizeof(argv0)); + retval = strlen(argv0); + snprintf(argv0+retval, sizeof(argv0)-retval, "/%s", argv[0]); + } + + /* Set an alternate signal stack so SIGSEGVs caused by stack overflows + * still run */ + altss.ss_sp = altstack; + altss.ss_flags = 0; + altss.ss_size = sizeof(altstack); + sigaltstack(&altss, NULL); + + memset(&sa, 0, sizeof(sa)); + sa.sa_sigaction = crash_catcher; + sa.sa_flags = SA_RESETHAND | SA_NODEFER | SA_SIGINFO | SA_ONSTACK; + sigemptyset(&sa.sa_mask); + + retval = 0; + while(num_signals--) + { + if((*signals != SIGSEGV && *signals != SIGILL && *signals != SIGFPE && *signals != SIGABRT && + *signals != SIGBUS) || sigaction(*signals, &sa, NULL) == -1) + { + *signals = 0; + retval = -1; + } + ++signals; + } + return retval; +} + + + + +static void* +test_trace(void* ignored) +{ + return (void*)ptrace(PTRACE_TRACEME, 0, NULL, NULL); +} + +bool +is_debugger_attached(void) +{ + pthread_attr_t attr; + void* result; + pthread_t thread; + + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + if (pthread_create(&thread, &attr, test_trace, NULL) != 0) { + pthread_attr_destroy(&attr); + return false; + } + pthread_attr_destroy(&attr); + if (pthread_join(thread, &result) != 0) { + return false; + } + + return result != NULL; +} diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 33f740b311..672a65d37f 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -16,6 +16,12 @@ #endif + +#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX || OGRE_PLATFORM == OGRE_PLATFORM_APPLE +extern int cc_install_handlers(int argc, char **argv, int num_signals, int *sigs, const char *logfile, int (*user_info)(char*, char*)); +extern int is_debugger_attached(void); +#endif + // for Ogre::macBundlePath #if OGRE_PLATFORM == OGRE_PLATFORM_APPLE #include @@ -239,6 +245,18 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat int main(int argc, char**argv) { +#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX || OGRE_PLATFORM == OGRE_PLATFORM_APPLE + // Unix crash catcher + if (!is_debugger_attached()) + { + int s[5] = { SIGSEGV, SIGILL, SIGFPE, SIGBUS, SIGABRT }; + cc_install_handlers(argc, argv, 5, s, "crash.log", NULL); + std::cout << "Installing crash catcher" << std::endl; + } + else + std::cout << "Running in a debugger, not installing crash catcher" << std::endl; +#endif + #if OGRE_PLATFORM == OGRE_PLATFORM_APPLE // set current dir to bundle path boost::filesystem::path bundlePath = boost::filesystem::path(Ogre::macBundlePath()).parent_path(); From 883140babfb48b4bf670a667d02163cee85645b8 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 16 Nov 2013 17:00:26 +0100 Subject: [PATCH 287/434] Add missing include for signals to make travis happy --- apps/openmw/main.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 672a65d37f..4f77c1b1ad 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -18,6 +18,7 @@ #if OGRE_PLATFORM == OGRE_PLATFORM_LINUX || OGRE_PLATFORM == OGRE_PLATFORM_APPLE +#include extern int cc_install_handlers(int argc, char **argv, int num_signals, int *sigs, const char *logfile, int (*user_info)(char*, char*)); extern int is_debugger_attached(void); #endif From 20d806b40ca38ea6c52d7cee1efdb0a26f953076 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 16 Nov 2013 18:15:54 +0100 Subject: [PATCH 288/434] Change is_debugger_attached to a different hack --- apps/openmw/crashcatcher.cpp | 29 ++++++++--------------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/apps/openmw/crashcatcher.cpp b/apps/openmw/crashcatcher.cpp index 2a1abdcec7..6663306663 100644 --- a/apps/openmw/crashcatcher.cpp +++ b/apps/openmw/crashcatcher.cpp @@ -432,31 +432,18 @@ int cc_install_handlers(int argc, char **argv, int num_signals, int *signals, co } - - -static void* -test_trace(void* ignored) -{ - return (void*)ptrace(PTRACE_TRACEME, 0, NULL, NULL); -} - +// gdb apparently opens FD(s) 3,4,5 (whereas a typical prog uses only stdin=0, stdout=1,stderr=2) bool is_debugger_attached(void) { - pthread_attr_t attr; - void* result; - pthread_t thread; + bool rc = false; + FILE *fd = fopen("/tmp", "r"); - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); - if (pthread_create(&thread, &attr, test_trace, NULL) != 0) { - pthread_attr_destroy(&attr); - return false; - } - pthread_attr_destroy(&attr); - if (pthread_join(thread, &result) != 0) { - return false; + if (fileno(fd) > 5) + { + rc = true; } - return result != NULL; + fclose(fd); + return rc; } From b2d2a3dd178a9ebffeec9d1120d3f3aa3638b2a1 Mon Sep 17 00:00:00 2001 From: vorenon Date: Sat, 16 Nov 2013 19:15:47 +0000 Subject: [PATCH 289/434] Closing message boxes with numpad enter --- apps/openmw/mwinput/inputmanagerimp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 35487e3391..1c4ff161f1 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -503,7 +503,7 @@ namespace MWInput mInputBinder->keyPressed (arg); - if(arg.keysym.sym == SDLK_RETURN + if(arg.keysym.sym == SDLK_RETURN || arg.keysym.sym == SDLK_KP_ENTER && MWBase::Environment::get().getWindowManager()->isGuiMode()) { // Pressing enter when a messagebox is prompting for "ok" will activate the ok button From a19242b4ec0d718e019bc2f8a66a326379091053 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 16 Nov 2013 19:31:35 +0100 Subject: [PATCH 290/434] fixed expression in if statement --- apps/openmw/mwinput/inputmanagerimp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 1c4ff161f1..9b50be7cab 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -503,7 +503,7 @@ namespace MWInput mInputBinder->keyPressed (arg); - if(arg.keysym.sym == SDLK_RETURN || arg.keysym.sym == SDLK_KP_ENTER + if((arg.keysym.sym == SDLK_RETURN || arg.keysym.sym == SDLK_KP_ENTER) && MWBase::Environment::get().getWindowManager()->isGuiMode()) { // Pressing enter when a messagebox is prompting for "ok" will activate the ok button From f990ba09f01edd2cfe4936838d6787e580b74d8c Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 16 Nov 2013 20:55:41 +0100 Subject: [PATCH 291/434] gdb detection doesn't seem to work for the forked process --- apps/openmw/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 4f77c1b1ad..a36b6e12f5 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -248,7 +248,7 @@ int main(int argc, char**argv) { #if OGRE_PLATFORM == OGRE_PLATFORM_LINUX || OGRE_PLATFORM == OGRE_PLATFORM_APPLE // Unix crash catcher - if (!is_debugger_attached()) + if ((argc == 2 && strcmp(argv[1], "--cc-handle-crash") == 0) || !is_debugger_attached()) { int s[5] = { SIGSEGV, SIGILL, SIGFPE, SIGBUS, SIGABRT }; cc_install_handlers(argc, argv, 5, s, "crash.log", NULL); From 7eb1dcb682ab5aaf96542f5c81e29e0c8f99a955 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 16 Nov 2013 22:29:40 +0100 Subject: [PATCH 292/434] Fix trade windows crashing after a new game --- apps/openmw/mwgui/container.cpp | 6 ++++-- apps/openmw/mwgui/tradewindow.cpp | 5 ++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index 5d864752fc..19ed4dbc00 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -220,11 +220,13 @@ namespace MWGui mDisposeCorpseButton->setVisible(loot); - setTitle(MWWorld::Class::get(container).getName(container)); - mSortModel = new SortFilterItemModel(mModel); mItemView->setModel (mSortModel); + + // Careful here. setTitle may cause size updates, causing itemview redraw, so make sure to do it last + // or we end up using a possibly invalid model. + setTitle(MWWorld::Class::get(container).getName(container)); } void ContainerWindow::onCloseButtonClicked(MyGUI::Widget* _sender) diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index c179236087..65e3917ed5 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -82,7 +82,6 @@ namespace MWGui void TradeWindow::startTrade(const MWWorld::Ptr& actor) { mPtr = actor; - setTitle(MWWorld::Class::get(actor).getName(actor)); mCurrentBalance = 0; mCurrentMerchantOffer = 0; @@ -99,6 +98,10 @@ namespace MWGui mItemView->setModel (mSortModel); updateLabels(); + + // Careful here. setTitle may cause size updates, causing itemview redraw, so make sure to do it last + // or we end up using a possibly invalid model. + setTitle(MWWorld::Class::get(actor).getName(actor)); } void TradeWindow::onFilterChanged(MyGUI::Widget* _sender) From 4a816b6c179e8a63237abbbd773c83bb596cd7a7 Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Sat, 16 Nov 2013 23:08:03 +0100 Subject: [PATCH 293/434] fix context menu --- apps/opencs/view/world/table.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index d4ccba4a7b..71bdb9000e 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -49,10 +49,10 @@ void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) { int row =selectedRows.begin()->row(); - int column = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Topic); + int column = mModel->searchColumnIndex (CSMWorld::Columns::ColumnId_Topic); if (column==-1) - column = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Journal); + column = mModel->searchColumnIndex (CSMWorld::Columns::ColumnId_Journal); if (column!=-1) { @@ -410,4 +410,4 @@ void CSVWorld::Table::requestFocus (const std::string& id) void CSVWorld::Table::recordFilterChanged (boost::shared_ptr filter) { mProxyModel->setFilter (filter); -} \ No newline at end of file +} From 35434dc05375feede36d8debd449a30b32bbeda4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20S=C3=B6derberg?= Date: Sat, 16 Nov 2013 23:15:51 +0100 Subject: [PATCH 294/434] German fixes Fixes for German language. --- files/mygui/openmw_chargen_birth.layout | 10 ++--- files/mygui/openmw_chargen_class.layout | 44 +++++++++--------- .../mygui/openmw_chargen_create_class.layout | 45 ++++++++++--------- files/mygui/openmw_chargen_review.layout | 42 ++++++++--------- 4 files changed, 71 insertions(+), 70 deletions(-) diff --git a/files/mygui/openmw_chargen_birth.layout b/files/mygui/openmw_chargen_birth.layout index b368a6407c..d13a5a02de 100644 --- a/files/mygui/openmw_chargen_birth.layout +++ b/files/mygui/openmw_chargen_birth.layout @@ -1,20 +1,20 @@ - + - + - + - + - + diff --git a/files/mygui/openmw_chargen_class.layout b/files/mygui/openmw_chargen_class.layout index 3c0348b663..aae3c70354 100644 --- a/files/mygui/openmw_chargen_class.layout +++ b/files/mygui/openmw_chargen_class.layout @@ -1,20 +1,20 @@ - + - + - + - + - + @@ -22,12 +22,12 @@ - + - + @@ -35,37 +35,37 @@ - - + + - + - - - - - + + + + + - + - - - - - + + + + + - + diff --git a/files/mygui/openmw_chargen_create_class.layout b/files/mygui/openmw_chargen_create_class.layout index 92382640b9..890e2aac17 100644 --- a/files/mygui/openmw_chargen_create_class.layout +++ b/files/mygui/openmw_chargen_create_class.layout @@ -1,13 +1,14 @@ - + - + + - + @@ -22,12 +23,12 @@ - + - + @@ -35,37 +36,37 @@ - - + + - - + + - - - - - + + + + + - - + + - - - - - + + + + + - + diff --git a/files/mygui/openmw_chargen_review.layout b/files/mygui/openmw_chargen_review.layout index 5d18f4bff8..db55e87546 100644 --- a/files/mygui/openmw_chargen_review.layout +++ b/files/mygui/openmw_chargen_review.layout @@ -1,10 +1,10 @@ - + - + @@ -17,27 +17,27 @@ - - - - + + + + - - + + - + - + @@ -46,57 +46,57 @@ - - + + - + - + - + - + - + - + - + @@ -106,12 +106,12 @@ - + - + From b8f5cd6cb759644ed949e41da924c62fcd3cbb27 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 16 Nov 2013 23:55:54 +0100 Subject: [PATCH 295/434] Forgot to apply the same fix to companion window --- apps/openmw/mwgui/companionwindow.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/companionwindow.cpp b/apps/openmw/mwgui/companionwindow.cpp index 9698608d69..a0a34108eb 100644 --- a/apps/openmw/mwgui/companionwindow.cpp +++ b/apps/openmw/mwgui/companionwindow.cpp @@ -86,12 +86,13 @@ void CompanionWindow::onBackgroundSelected() void CompanionWindow::open(const MWWorld::Ptr& npc) { mPtr = npc; - setTitle(MWWorld::Class::get(npc).getName(npc)); updateEncumbranceBar(); mModel = new CompanionItemModel(npc); mSortModel = new SortFilterItemModel(mModel); mItemView->setModel(mSortModel); + + setTitle(MWWorld::Class::get(npc).getName(npc)); } void CompanionWindow::onFrame() From 07251f0fa4319f694802837c04cc11790906f00a Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 17 Nov 2013 04:32:56 +0100 Subject: [PATCH 296/434] Don't apply spells to dead actors --- apps/openmw/mwworld/worldimp.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index e6c4c1ef08..2dd0a9b930 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2166,7 +2166,10 @@ namespace MWWorld if (!contact.first.isEmpty()) { if (contact.first.getClass().isActor()) - contact.first.getClass().getCreatureStats(contact.first).getActiveSpells().addSpell(selectedSpell, contact.first, actor, ESM::RT_Touch, sourceName); + { + if (!contact.first.getClass().getCreatureStats(contact.first).isDead()) + contact.first.getClass().getCreatureStats(contact.first).getActiveSpells().addSpell(selectedSpell, contact.first, actor, ESM::RT_Touch, sourceName); + } else { // We hit a non-actor, e.g. a door. Only instant effects are relevant. From e7993ced69af272b9eafb6605f2ada9c2ffbc675 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 17 Nov 2013 04:33:04 +0100 Subject: [PATCH 297/434] Fix invalid casts --- apps/openmw/mwrender/animation.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index cc92e62489..10c925b36a 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1045,7 +1045,9 @@ void Animation::updateEffects(float duration) NifOgre::ObjectList& objects = it->mObjects; for(size_t i = 0; i < objects.mControllers.size() ;i++) { - static_cast (objects.mControllers[i].getSource().get())->addTime(duration); + EffectAnimationValue* value = dynamic_cast(objects.mControllers[i].getSource().get()); + if (value) + value->addTime(duration); objects.mControllers[i].update(); } @@ -1058,7 +1060,9 @@ void Animation::updateEffects(float duration) float remainder = objects.mControllers[0].getSource()->getValue() - objects.mMaxControllerLength; for(size_t i = 0; i < objects.mControllers.size() ;i++) { - static_cast (objects.mControllers[i].getSource().get())->resetTime(remainder); + EffectAnimationValue* value = dynamic_cast(objects.mControllers[i].getSource().get()); + if (value) + value->resetTime(remainder); } } else From b8c358df63a079b29096cdc811dc991665fd8498 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 17 Nov 2013 13:37:19 +0100 Subject: [PATCH 298/434] Fix a bug with equipping using the quick keys: when item is already equipped, equipping it again would cause the slot to get unequipped first, possibly restacking and destroying the Ptr that was supposed to be equipped in the first place --- apps/openmw/mwworld/actionequip.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwworld/actionequip.cpp b/apps/openmw/mwworld/actionequip.cpp index 2a50b8a600..0d091e7425 100644 --- a/apps/openmw/mwworld/actionequip.cpp +++ b/apps/openmw/mwworld/actionequip.cpp @@ -60,6 +60,9 @@ namespace MWWorld for (std::vector::const_iterator slot=slots_.first.begin(); slot!=slots_.first.end(); ++slot) { + // if the item is equipped already, nothing to do + if (invStore.getSlot(*slot) == it) + return; // if all slots are occupied, replace the last slot if (slot == --slots_.first.end()) From 2a11618ee7b4b6370f2ebe3d3898df43a7721006 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 17 Nov 2013 13:38:54 +0100 Subject: [PATCH 299/434] Make sure the equipped weapon HUD icon is updated properly --- apps/openmw/mwgui/inventorywindow.cpp | 8 -------- apps/openmw/mwgui/quickkeysmenu.cpp | 3 ++- apps/openmw/mwmechanics/character.cpp | 6 ++---- .../openmw/mwmechanics/mechanicsmanagerimp.cpp | 2 +- apps/openmw/mwworld/inventorystore.cpp | 18 ++++++++++++------ 5 files changed, 17 insertions(+), 20 deletions(-) diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 8f8744917f..022e0a47d8 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -455,14 +455,6 @@ namespace MWGui if (MWBase::Environment::get().getWindowManager()->getSpellWindow()) MWBase::Environment::get().getWindowManager()->getSpellWindow()->updateSpells(); - // update selected weapon icon - MWWorld::InventoryStore& invStore = MWWorld::Class::get(mPtr).getInventoryStore(mPtr); - MWWorld::ContainerStoreIterator weaponSlot = invStore.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if (weaponSlot == invStore.end()) - MWBase::Environment::get().getWindowManager()->unsetSelectedWeapon(); - else - MWBase::Environment::get().getWindowManager()->setSelectedWeapon(*weaponSlot); - mPreviewDirty = true; mArmorRating->setCaptionWithReplacing ("#{sArmor}: " diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 503bf7c114..3956766499 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -284,8 +284,9 @@ namespace MWGui MWWorld::Ptr item = *button->getChildAt (0)->getUserData(); // make sure the item is available - if (item.getRefData ().getCount() == 0) + if (item.getRefData ().getCount() < 1) { + // TODO: Try to find a replacement with the same ID? MWBase::Environment::get().getWindowManager ()->messageBox ( "#{sQuickMenu5} " + MWWorld::Class::get(item).getName(item)); return; diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 240695a1cb..88c9f0ff6f 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -563,10 +563,8 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun if(!resultSound.empty()) MWBase::Environment::get().getSoundManager()->playSound(resultSound, 1.0f, 1.0f); - // tool used up? - if(!item.getRefData().getCount()) - MWBase::Environment::get().getWindowManager()->unsetSelectedWeapon(); - else + // Set again, just to update the charge bar + if(item.getRefData().getCount()) MWBase::Environment::get().getWindowManager()->setSelectedWeapon(item); } else diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index bbebcd6939..29b12b0f3a 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -159,7 +159,7 @@ namespace MWMechanics // auto-equip again. we need this for when the race is changed to a beast race MWWorld::InventoryStore& invStore = MWWorld::Class::get(ptr).getInventoryStore(ptr); for (int i=0; i=static_cast (mSlots.size())) throw std::runtime_error ("slot number out of range"); @@ -99,13 +102,11 @@ void MWWorld::InventoryStore::equip (int slot, const ContainerStoreIterator& ite throw std::runtime_error ("attempt to equip an item that is not in the inventory"); std::pair, bool> slots_; - if (iterator!=end()) - { - slots_ = Class::get (*iterator).getEquipmentSlots (*iterator); - if (std::find (slots_.first.begin(), slots_.first.end(), slot)==slots_.first.end()) - throw std::runtime_error ("invalid slot"); - } + slots_ = Class::get (*iterator).getEquipmentSlots (*iterator); + + if (std::find (slots_.first.begin(), slots_.first.end(), slot)==slots_.first.end()) + throw std::runtime_error ("invalid slot"); if (mSlots[slot] != end()) unequipSlot(slot, actor); @@ -126,6 +127,10 @@ void MWWorld::InventoryStore::equip (int slot, const ContainerStoreIterator& ite fireEquipmentChangedEvent(); updateMagicEffects(actor); + + // Update HUD icon for player weapon + if (slot == MWWorld::InventoryStore::Slot_CarriedRight) + MWBase::Environment::get().getWindowManager()->setSelectedWeapon(*getSlot(slot)); } void MWWorld::InventoryStore::unequipAll(const MWWorld::Ptr& actor) @@ -482,6 +487,7 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, c if ((mSelectedEnchantItem != end()) && (mSelectedEnchantItem == it)) { // enchanted item + mSelectedEnchantItem = end(); MWBase::Environment::get().getWindowManager()->unsetSelectedSpell(); } } From 47fc3f81f520758dc618449b726ce765833dbd72 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 17 Nov 2013 14:09:42 +0100 Subject: [PATCH 300/434] Don't update actors while paused --- apps/openmw/mwmechanics/actors.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 2873816f73..6d31a810aa 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -336,6 +336,7 @@ namespace MWMechanics mDuration += duration; //if (mDuration>=0.25) + if (!paused) { float totalDuration = mDuration; mDuration = 0; From fc268bf3028e2815577ec9079c87cb0743a43088 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 17 Nov 2013 14:17:59 +0100 Subject: [PATCH 301/434] Initialize mFallHeight --- apps/openmw/mwmechanics/character.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 88c9f0ff6f..7df6ec566e 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -350,6 +350,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim , mSkipAnim(false) , mSecondsOfRunning(0) , mSecondsOfSwimming(0) + , mFallHeight(0) { if(!mAnimation) return; From 7fd5f1df83b934ea291c524cd8e84aa9e8d7851f Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 17 Nov 2013 22:33:37 +0100 Subject: [PATCH 302/434] Change setKeepParticlesInLocalSpace to false. Not correct for all particles, but the opposite isn't either. Plus it breaks pretty much all magic VFX. --- components/nifogre/ogrenifloader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/nifogre/ogrenifloader.cpp b/components/nifogre/ogrenifloader.cpp index 4b9cb5ad5b..bb98501f47 100644 --- a/components/nifogre/ogrenifloader.cpp +++ b/components/nifogre/ogrenifloader.cpp @@ -602,7 +602,7 @@ class NIFObjectLoader partsys->setParticleQuota(particledata->numParticles); // TODO: There is probably a field or flag to specify this, as some // particle effects have it and some don't. - partsys->setKeepParticlesInLocalSpace(true); + partsys->setKeepParticlesInLocalSpace(false); Nif::ControllerPtr ctrl = partnode->controller; while(!ctrl.empty()) From 0dc2e829dd45b66c2d02ba69e3de887a4472b8d6 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 17 Nov 2013 23:15:57 +0100 Subject: [PATCH 303/434] Lots of cleanup. Implemented Absorb and Resist. Implemented several instant effects. Added hand VFX. --- apps/openmw/mwbase/mechanicsmanager.hpp | 5 + apps/openmw/mwclass/npc.cpp | 17 +- apps/openmw/mwgui/spellicons.cpp | 6 +- apps/openmw/mwgui/spellicons.hpp | 2 +- apps/openmw/mwmechanics/activespells.cpp | 388 +++----------- apps/openmw/mwmechanics/activespells.hpp | 77 ++- apps/openmw/mwmechanics/actors.hpp | 4 + apps/openmw/mwmechanics/character.cpp | 6 + apps/openmw/mwmechanics/magiceffects.hpp | 14 +- .../mwmechanics/mechanicsmanagerimp.cpp | 5 + .../mwmechanics/mechanicsmanagerimp.hpp | 5 + apps/openmw/mwmechanics/spellcasting.cpp | 473 ++++++++++++++++++ apps/openmw/mwmechanics/spellcasting.hpp | 42 +- apps/openmw/mwmechanics/spells.cpp | 58 ++- apps/openmw/mwmechanics/spells.hpp | 5 + apps/openmw/mwrender/animation.cpp | 44 +- apps/openmw/mwrender/animation.hpp | 3 +- apps/openmw/mwrender/objects.cpp | 8 + apps/openmw/mwrender/objects.hpp | 2 + apps/openmw/mwrender/renderingmanager.cpp | 5 + apps/openmw/mwworld/actioneat.cpp | 28 +- apps/openmw/mwworld/actiontrap.cpp | 18 +- apps/openmw/mwworld/inventorystore.cpp | 23 +- apps/openmw/mwworld/worldimp.cpp | 150 +----- 24 files changed, 801 insertions(+), 587 deletions(-) create mode 100644 apps/openmw/mwmechanics/spellcasting.cpp diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 7e09f9b4d7..24dc569d8c 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -114,6 +114,11 @@ namespace MWBase /// references that are currently not in the scene should be ignored. virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) = 0; + + /// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently + /// paused we may want to do it manually (after equipping permanent enchantment) + virtual void updateMagicEffects (const MWWorld::Ptr& ptr) = 0; + }; } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index fd24dd93fc..5c67f17afb 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -20,6 +20,7 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/movement.hpp" +#include "../mwmechanics/spellcasting.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/actiontalk.hpp" @@ -467,12 +468,9 @@ namespace MWClass else { weapon.getCellRef().mEnchantmentCharge -= castCost; - // Touch - othercls.getCreatureStats(victim).getActiveSpells().addSpell(enchantmentName, victim, ptr, ESM::RT_Touch, weapon.getClass().getName(weapon)); - // Self - getCreatureStats(ptr).getActiveSpells().addSpell(enchantmentName, ptr, ptr, ESM::RT_Self, weapon.getClass().getName(weapon)); - // Target - MWBase::Environment::get().getWorld()->launchProjectile(enchantmentName, enchantment->mEffects, ptr, weapon.getClass().getName(weapon)); + + MWMechanics::CastSpell cast(ptr, victim); + cast.cast(weapon); } } } @@ -932,11 +930,8 @@ namespace MWClass bool Npc::apply (const MWWorld::Ptr& ptr, const std::string& id, const MWWorld::Ptr& actor) const { - MWMechanics::CreatureStats& stats = getCreatureStats (ptr); - - /// \todo consider instant effects - - return stats.getActiveSpells().addSpell (id, actor, actor); + MWMechanics::CastSpell cast(ptr, ptr); + return cast.cast(id); } void Npc::skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType) const diff --git a/apps/openmw/mwgui/spellicons.cpp b/apps/openmw/mwgui/spellicons.cpp index a18e88f5fd..e93e96c4b2 100644 --- a/apps/openmw/mwgui/spellicons.cpp +++ b/apps/openmw/mwgui/spellicons.cpp @@ -21,17 +21,17 @@ namespace MWGui { - void EffectSourceVisitor::visit (const ESM::ENAMstruct& enam, + void EffectSourceVisitor::visit (MWMechanics::EffectKey key, const std::string& sourceName, float magnitude, float remainingTime) { MagicEffectInfo newEffectSource; - newEffectSource.mKey = MWMechanics::EffectKey(enam); + newEffectSource.mKey = key; newEffectSource.mMagnitude = magnitude; newEffectSource.mPermanent = mIsPermanent; newEffectSource.mRemainingTime = remainingTime; newEffectSource.mSource = sourceName; - mEffectSources[enam.mEffectID].push_back(newEffectSource); + mEffectSources[key.mId].push_back(newEffectSource); } diff --git a/apps/openmw/mwgui/spellicons.hpp b/apps/openmw/mwgui/spellicons.hpp index bae108a1da..a29e2a00ab 100644 --- a/apps/openmw/mwgui/spellicons.hpp +++ b/apps/openmw/mwgui/spellicons.hpp @@ -42,7 +42,7 @@ namespace MWGui std::map > mEffectSources; - virtual void visit (const ESM::ENAMstruct& enam, + virtual void visit (MWMechanics::EffectKey key, const std::string& sourceName, float magnitude, float remainingTime = -1); }; diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index 89543476b0..dc79901b0e 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -1,31 +1,7 @@ - #include "activespells.hpp" -#include - -#include - -#include -#include -#include -#include -#include - -#include "../mwworld/esmstore.hpp" - #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" -#include "../mwbase/soundmanager.hpp" -#include "../mwbase/windowmanager.hpp" - -#include "../mwrender/animation.hpp" - -#include "../mwworld/class.hpp" - -#include "../mwmechanics/spellcasting.hpp" - -#include "creaturestats.hpp" -#include "npcstats.hpp" namespace MWMechanics { @@ -35,6 +11,7 @@ namespace MWMechanics MWWorld::TimeStamp now = MWBase::Environment::get().getWorld()->getTimeStamp(); + // Erase no longer active spells if (mLastUpdate!=now) { TContainer::iterator iter (mSpells.begin()); @@ -42,7 +19,6 @@ namespace MWMechanics if (!timeToExpire (iter)) { mSpells.erase (iter++); - //onSpellExpired rebuild = true; } else @@ -69,277 +45,28 @@ namespace MWMechanics for (TIterator iter (begin()); iter!=end(); ++iter) { - std::pair > effects = getEffectList (iter->first); - const MWWorld::TimeStamp& start = iter->second.mTimeStamp; - int i = 0; - for (std::vector::const_iterator effectIter (effects.first.mList.begin()); - effectIter!=effects.first.mList.end(); ++effectIter, ++i) + const std::vector& effects = iter->second.mEffects; + + for (std::vector::const_iterator effectIt = effects.begin(); effectIt != effects.end(); ++effectIt) { - float random = iter->second.mRandom[i]; - if (effectIter->mRange != iter->second.mRange) - continue; + int duration = effectIt->mDuration; + MWWorld::TimeStamp end = start; + end += static_cast (duration)* + MWBase::Environment::get().getWorld()->getTimeScaleFactor()/(60*60); - if (effectIter->mDuration) - { - int duration = effectIter->mDuration; - - if (effects.second.first) - duration *= random; - - MWWorld::TimeStamp end = start; - end += static_cast (duration)* - MWBase::Environment::get().getWorld()->getTimeScaleFactor()/(60*60); - - if (end>now) - { - EffectParam param; - - if (effects.second.first) - { - const ESM::MagicEffect *magicEffect = - MWBase::Environment::get().getWorld()->getStore().get().find ( - effectIter->mEffectID); - - if (effectIter->mDuration==0) - { - param.mMagnitude = - static_cast (random / (0.1 * magicEffect->mData.mBaseCost)); - } - else - { - param.mMagnitude = - static_cast (0.05*random / (0.1 * magicEffect->mData.mBaseCost)); - } - } - else - param.mMagnitude = static_cast ( - (effectIter->mMagnMax-effectIter->mMagnMin)*random + effectIter->mMagnMin); - param.mMagnitude *= iter->second.mMultiplier[i]; - - if (param.mMagnitude) - mEffects.add (*effectIter, param); - } - } + if (end>now) + mEffects.add(effectIt->mKey, MWMechanics::EffectParam(effectIt->mMagnitude)); } } } - std::pair > ActiveSpells::getEffectList (const std::string& id) const - { - if (const ESM::Enchantment* enchantment = - MWBase::Environment::get().getWorld()->getStore().get().search (id)) - return std::make_pair (enchantment->mEffects, std::make_pair(false, false)); - - if (const ESM::Spell *spell = - MWBase::Environment::get().getWorld()->getStore().get().search (id)) - return std::make_pair (spell->mEffects, std::make_pair(false, false)); - - if (const ESM::Potion *potion = - MWBase::Environment::get().getWorld()->getStore().get().search (id)) - return std::make_pair (potion->mEffects, std::make_pair(false, true)); - - if (const ESM::Ingredient *ingredient = - MWBase::Environment::get().getWorld()->getStore().get().search (id)) - { - const ESM::MagicEffect *magicEffect = - MWBase::Environment::get().getWorld()->getStore().get().find ( - ingredient->mData.mEffectID[0]); - - ESM::ENAMstruct effect; - effect.mEffectID = ingredient->mData.mEffectID[0]; - effect.mSkill = ingredient->mData.mSkills[0]; - effect.mAttribute = ingredient->mData.mAttributes[0]; - effect.mRange = 0; - effect.mArea = 0; - effect.mDuration = magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration ? 0 : 1; - effect.mMagnMin = 1; - effect.mMagnMax = 1; - - std::pair > result; - result.second.second = true; - result.second.first = true; - - result.first.mList.push_back (effect); - - return result; - } - - throw std::runtime_error ("ID " + id + " can not produce lasting effects"); - } - - std::string ActiveSpells::getSpellDisplayName (const std::string& id) const - { - if (const ESM::Spell *spell = - MWBase::Environment::get().getWorld()->getStore().get().search (id)) - return spell->mName; - - if (const ESM::Potion *potion = - MWBase::Environment::get().getWorld()->getStore().get().search (id)) - return potion->mName; - - if (const ESM::Ingredient *ingredient = - MWBase::Environment::get().getWorld()->getStore().get().search (id)) - return ingredient->mName; - - throw std::runtime_error ("ID " + id + " has no display name"); - } - ActiveSpells::ActiveSpells() - : mSpellsChanged (false), mLastUpdate (MWBase::Environment::get().getWorld()->getTimeStamp()) + : mSpellsChanged (false) + , mLastUpdate (MWBase::Environment::get().getWorld()->getTimeStamp()) {} - bool ActiveSpells::addSpell (const std::string& id, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, ESM::RangeType range, const std::string& name, int effectIndex) - { - const CreatureStats& creatureStats = MWWorld::Class::get (actor).getCreatureStats (actor); - - std::pair > effects = getEffectList (id); - bool stacks = effects.second.second; - - bool found = false; - - for (std::vector::const_iterator iter (effects.first.mList.begin()); - iter!=effects.first.mList.end(); ++iter) - { - if (iter->mRange != range) - continue; - if (iter->mDuration) - { - found = true; - break; - } - } - - // If none of the effects need to apply, no need to add the spell - if (!found) - return false; - - TContainer::iterator iter = mSpells.find (id); - - ActiveSpellParams params; - for (unsigned int i=0; i (std::rand()) / RAND_MAX; - if (effects.second.first) - { - // ingredient -> special treatment required. - const NpcStats& npcStats = MWWorld::Class::get (actor).getNpcStats (actor); - - float x = - (npcStats.getSkill (ESM::Skill::Alchemy).getModified() + - 0.2 * creatureStats.getAttribute (1).getModified() - + 0.1 * creatureStats.getAttribute (7).getModified()) - * creatureStats.getFatigueTerm(); - random *= 100; - random = random / std::min (x, 100.0f); - random *= 0.25 * x; - } - - params.mRandom.push_back(random); - } - params.mRange = range; - params.mTimeStamp = MWBase::Environment::get().getWorld()->getTimeStamp(); - params.mName = name; - params.mMultiplier.resize(effects.first.mList.size(), 1); - - /* - for (int i=0; imRange != range) - { - params.mDisabled.push_back(true); - continue; - } - - bool disabled = false; - - int reflect = creatureStats.getMagicEffects().get(ESM::MagicEffect::Reflect).mMagnitude; - int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] - if (roll < reflect) - disabled = true; - } - */ - - bool first=true; - int i = 0; - for (std::vector::const_iterator effectIt (effects.first.mList.begin()); - effectIt!=effects.first.mList.end(); ++effectIt, ++i) - { - if (effectIt->mRange != range) - continue; - - const ESM::MagicEffect *magicEffect = - MWBase::Environment::get().getWorld()->getStore().get().find ( - effectIt->mEffectID); - - if (caster.getRefData().getHandle() == "player" && actor != caster - && magicEffect->mData.mFlags & ESM::MagicEffect::Harmful) - MWBase::Environment::get().getWindowManager()->setEnemy(actor); - - // Try resisting effect in case its harmful - const ESM::Spell *spell = - MWBase::Environment::get().getWorld()->getStore().get().search (id); - params.mMultiplier[i] = MWMechanics::getEffectMultiplier(effectIt->mEffectID, actor, caster, spell); - if (params.mMultiplier[i] == 0) - { - if (actor.getRefData().getHandle() == "player") - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); - else - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}"); - } - - // If fully resisted, don't play sounds or particles - if (params.mMultiplier[i] == 0) - continue; - - // TODO: For Area effects, launch a growing particle effect that applies the effect to more actors as it hits them. Best managed in World. - - // Only the sound of the first effect plays - if (first) - { - static const std::string schools[] = { - "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" - }; - - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - if(!magicEffect->mHitSound.empty()) - sndMgr->playSound3D(actor, magicEffect->mHitSound, 1.0f, 1.0f); - else - sndMgr->playSound3D(actor, schools[magicEffect->mData.mSchool]+" hit", 1.0f, 1.0f); - } - - if (!magicEffect->mHit.empty()) - { - const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get().find (magicEffect->mHit); - bool loop = magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx; - MWBase::Environment::get().getWorld()->getAnimation(actor)->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, ""); - } - - first = false; - } - - if (iter==mSpells.end() || stacks) - mSpells.insert (std::make_pair (id, params)); - else - iter->second = params; - - mSpellsChanged = true; - - return true; - } - - void ActiveSpells::removeSpell (const std::string& id) - { - TContainer::iterator iter = mSpells.find (id); - - if (iter!=mSpells.end()) - { - mSpells.erase (iter); - mSpellsChanged = true; - } - } - const MagicEffects& ActiveSpells::getMagicEffects() const { update(); @@ -348,37 +75,31 @@ namespace MWMechanics ActiveSpells::TIterator ActiveSpells::begin() const { - update(); return mSpells.begin(); } ActiveSpells::TIterator ActiveSpells::end() const { - update(); return mSpells.end(); } double ActiveSpells::timeToExpire (const TIterator& iterator) const { - std::pair > effects = getEffectList (iterator->first); + const std::vector& effects = iterator->second.mEffects; int duration = 0; - for (std::vector::const_iterator iter (effects.first.mList.begin()); - iter!=effects.first.mList.end(); ++iter) + for (std::vector::const_iterator iter (effects.begin()); + iter!=effects.end(); ++iter) { if (iter->mDuration > duration) duration = iter->mDuration; } - // Scale duration by magnitude if needed - if (effects.second.first && iterator->second.mRandom.size()) - duration *= iterator->second.mRandom.front(); - double scaledDuration = duration * MWBase::Environment::get().getWorld()->getTimeScaleFactor()/(60*60); - double usedUp = MWBase::Environment::get().getWorld()->getTimeStamp()-iterator->second.mTimeStamp; + double usedUp = MWBase::Environment::get().getWorld()->getTimeStamp() - iterator->second.mTimeStamp; if (usedUp>=scaledDuration) return 0; @@ -405,48 +126,67 @@ namespace MWMechanics return mSpells; } + void ActiveSpells::addSpell(const std::string &id, bool stack, std::vector effects, const std::string &displayName) + { + bool exists = false; + for (TContainer::const_iterator it = begin(); it != end(); ++it) + { + if (id == it->first) + exists = true; + } + + ActiveSpellParams params; + params.mTimeStamp = MWBase::Environment::get().getWorld()->getTimeStamp(); + params.mEffects = effects; + params.mDisplayName = displayName; + + if (!exists || stack) + mSpells.insert (std::make_pair(id, params)); + else + mSpells.find(id)->second = params; + + mSpellsChanged = true; + } + void ActiveSpells::visitEffectSources(EffectSourceVisitor &visitor) const { for (TContainer::const_iterator it = begin(); it != end(); ++it) { - const ESM::EffectList& list = getEffectList(it->first).first; - float timeScale = MWBase::Environment::get().getWorld()->getTimeScaleFactor(); - int i=0; - for (std::vector::const_iterator effectIt = list.mList.begin(); - effectIt != list.mList.end(); ++effectIt, ++i) + for (std::vector::const_iterator effectIt = it->second.mEffects.begin(); + effectIt != it->second.mEffects.end(); ++effectIt) { - if (effectIt->mRange != it->second.mRange) - continue; - - std::string name; - if (it->second.mName.empty()) - name = getSpellDisplayName(it->first); - else - name = it->second.mName; + std::string name = it->second.mDisplayName; float remainingTime = effectIt->mDuration + (it->second.mTimeStamp - MWBase::Environment::get().getWorld()->getTimeStamp())*3600/timeScale; - float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * it->second.mRandom[i]; + float magnitude = effectIt->mMagnitude; - // hack for ingredients - if (MWBase::Environment::get().getWorld()->getStore().get().search (it->first)) - { - const ESM::MagicEffect *magicEffect = - MWBase::Environment::get().getWorld()->getStore().get().find ( - effectIt->mEffectID); - - remainingTime = effectIt->mDuration * it->second.mRandom[i] + - (it->second.mTimeStamp - MWBase::Environment::get().getWorld()->getTimeStamp())*3600/timeScale; - - magnitude = static_cast (0.05*it->second.mRandom[i] / (0.1 * magicEffect->mData.mBaseCost)); - } - - magnitude *= it->second.mMultiplier[i]; if (magnitude) - visitor.visit(*effectIt, name, magnitude, remainingTime); + visitor.visit(effectIt->mKey, name, magnitude, remainingTime); } } } + + void ActiveSpells::purgeAll() + { + mSpells.clear(); + } + + void ActiveSpells::purgeEffect(short effectId) + { + for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it) + { + for (std::vector::iterator effectIt = it->second.mEffects.begin(); + effectIt != it->second.mEffects.end();) + { + if (effectIt->mKey.mId == effectId) + effectIt = it->second.mEffects.erase(effectIt); + else + effectIt++; + } + } + + } } diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index 56d9413c39..001402337c 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -11,39 +11,8 @@ #include -namespace ESM -{ - struct Spell; - struct EffectList; -} - -namespace MWWorld -{ - class Ptr; -} - namespace MWMechanics { - struct ActiveSpellParams - { - // Only apply effects of this range type - ESM::RangeType mRange; - - // When the spell was added - MWWorld::TimeStamp mTimeStamp; - - // Random factor for each effect - std::vector mRandom; - - // Effect magnitude multiplier. Use 0 to completely disable the effect - // (if it was resisted, reflected or absorbed). Use (0,1) for partially resisted. - std::vector mMultiplier; - - // Display name, we need this for enchantments, which don't have a name - so you need to supply the - // name of the item with the enchantment to addSpell - std::string mName; - }; - /// \brief Lasting spell effects /// /// \note The name of this class is slightly misleading, since it also handels lasting potion @@ -52,6 +21,23 @@ namespace MWMechanics { public: + // Parameters of an effect concerning lasting effects. + // Note we are not using ENAMstruct since the magnitude may be modified by magic resistance, etc. + // It could also be a negative magnitude, in case of inversing an effect, e.g. Absorb spell causes damage on target, but heals the caster. + struct Effect + { + float mMagnitude; + EffectKey mKey; + float mDuration; + }; + + struct ActiveSpellParams + { + std::vector mEffects; + MWWorld::TimeStamp mTimeStamp; + std::string mDisplayName; + }; + typedef std::multimap TContainer; typedef TContainer::const_iterator TIterator; @@ -66,9 +52,6 @@ namespace MWMechanics void rebuildEffects() const; - std::pair > getEffectList (const std::string& id) const; - ///< @return (EffectList, (isIngredient, stacks)) - double timeToExpire (const TIterator& iterator) const; ///< Returns time (in in-game hours) until the spell pointed to by \a iterator /// expires. @@ -79,25 +62,25 @@ namespace MWMechanics TIterator end() const; - std::string getSpellDisplayName (const std::string& id) const; - public: ActiveSpells(); - bool addSpell (const std::string& id, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, ESM::RangeType range = ESM::RT_Self, const std::string& name = "", int effectIndex = -1); - ///< Overwrites an existing spell with the same ID. If the spell does not have any - /// non-instant effects, it is ignored. - /// @param id - /// @param actor actor to add the spell to - /// @param caster actor who casted the spell - /// @param range Only effects with range type \a range will be applied - /// @param name Display name for enchantments, since they don't have a name in their record - /// @param effectIndex Only apply one specific effect - useful for reflecting spells, since each effect is reflected individually + /// Add lasting effects /// - /// \return Has the spell been added? + /// \brief addSpell + /// \param id ID for stacking purposes. + /// \param stack If false, the spell is not added if one with the same ID exists already. + /// \param effects + /// \param displayName Name for display in magic menu. + /// + void addSpell (const std::string& id, bool stack, std::vector effects, const std::string& displayName); - void removeSpell (const std::string& id); + /// Remove all active effects with this id + void purgeEffect (short effectId); + + /// Remove all active effects + void purgeAll (); bool isSpellActive (std::string id) const; ///< case insensitive diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 251c38ec0e..01a96f1993 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -53,6 +53,10 @@ namespace MWMechanics Actors(); + /// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently + /// paused we may want to do it manually (after equipping permanent enchantment) + void updateMagicEffects (const MWWorld::Ptr& ptr) { adjustMagicEffects(ptr); } + void addActor (const MWWorld::Ptr& ptr); ///< Register an actor for stats management /// diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 7df6ec566e..c7a6b38751 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -516,6 +516,12 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun const ESM::Static* castStatic = store.get().find (effect->mCasting); mAnimation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex); + castStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Hands"); + //mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Bip01 L Hand", effect->mParticle); + //mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Bip01 R Hand", effect->mParticle); + mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Left Hand", effect->mParticle); + mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Right Hand", effect->mParticle); + switch(effectentry.mRange) { case 0: mAttackType = "self"; break; diff --git a/apps/openmw/mwmechanics/magiceffects.hpp b/apps/openmw/mwmechanics/magiceffects.hpp index 58f023ebac..2c1b363b7d 100644 --- a/apps/openmw/mwmechanics/magiceffects.hpp +++ b/apps/openmw/mwmechanics/magiceffects.hpp @@ -12,13 +12,6 @@ namespace ESM namespace MWMechanics { - // Used by effect management classes (ActiveSpells, InventoryStore, Spells) to list active effect sources for GUI display - struct EffectSourceVisitor - { - virtual void visit (const ESM::ENAMstruct& enam, - const std::string& sourceName, float magnitude, float remainingTime = -1) = 0; - }; - struct EffectKey { int mId; @@ -59,6 +52,13 @@ namespace MWMechanics return param -= right; } + // Used by effect management classes (ActiveSpells, InventoryStore, Spells) to list active effect sources for GUI display + struct EffectSourceVisitor + { + virtual void visit (MWMechanics::EffectKey key, + const std::string& sourceName, float magnitude, float remainingTime = -1) = 0; + }; + /// \brief Effects currently affecting a NPC or creature class MagicEffects { diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 29b12b0f3a..e1a7ac1231 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -679,4 +679,9 @@ namespace MWMechanics return false; } + void MechanicsManager::updateMagicEffects(const MWWorld::Ptr &ptr) + { + mActors.updateMagicEffects(ptr); + } + } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index aedb84b29c..42656d5abc 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -100,6 +100,11 @@ namespace MWMechanics virtual void playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number); virtual void skipAnimation(const MWWorld::Ptr& ptr); virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string &groupName); + + /// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently + /// paused we may want to do it manually (after equipping permanent enchantment) + virtual void updateMagicEffects (const MWWorld::Ptr& ptr); + }; } diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp new file mode 100644 index 0000000000..00ca82c5af --- /dev/null +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -0,0 +1,473 @@ +#include "spellcasting.hpp" + +#include + +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/soundmanager.hpp" + + +#include "../mwworld/containerstore.hpp" + +#include "../mwrender/animation.hpp" + +namespace MWMechanics +{ + + CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target) + : mCaster(caster) + , mTarget(target) + , mStack(false) + { + } + + void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, + const ESM::EffectList &effects, ESM::RangeType range, bool reflected) + { + // If none of the effects need to apply, we can early-out + bool found = false; + for (std::vector::const_iterator iter (effects.mList.begin()); + iter!=effects.mList.end(); ++iter) + { + if (iter->mRange != range) + continue; + found = true; + } + if (!found) + return; + + ESM::EffectList reflectedEffects; + std::vector appliedLastingEffects; + bool firstAppliedEffect = true; + + for (std::vector::const_iterator effectIt (effects.mList.begin()); + effectIt!=effects.mList.end(); ++effectIt) + { + if (effectIt->mRange != range) + continue; + + const ESM::MagicEffect *magicEffect = + MWBase::Environment::get().getWorld()->getStore().get().find ( + effectIt->mEffectID); + + float magnitudeMult = 1; + if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful && target.getClass().isActor()) + { + // If player is attempting to cast a harmful spell, show the target's HP bar + if (caster.getRefData().getHandle() == "player" && target != caster) + MWBase::Environment::get().getWindowManager()->setEnemy(target); + + // Try absorbing if it's a spell + // NOTE: Vanilla does this once per effect source instead of adding the % from all sources together, not sure + // if that is worth replicating. + if (const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search (mId)) + { + int absorb = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).mMagnitude; + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + bool isAbsorbed = (roll < absorb); + if (isAbsorbed) + { + const ESM::Static* absorbStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Absorb"); + MWBase::Environment::get().getWorld()->getAnimation(target)->addEffect( + "meshes\\" + absorbStatic->mModel, ESM::MagicEffect::Reflect, false, ""); + // Magicka is increased by cost of spell + DynamicStat magicka = target.getClass().getCreatureStats(target).getMagicka(); + magicka.setCurrent(magicka.getCurrent() + spell->mData.mCost); + target.getClass().getCreatureStats(target).setMagicka(magicka); + magnitudeMult = 0; + } + } + + // Try reflecting + if (!reflected && magnitudeMult > 0) + { + int reflect = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Reflect).mMagnitude; + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + bool isReflected = (roll < reflect); + if (isReflected) + { + const ESM::Static* reflectStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Reflect"); + MWBase::Environment::get().getWorld()->getAnimation(target)->addEffect( + "meshes\\" + reflectStatic->mModel, ESM::MagicEffect::Reflect, false, ""); + reflectedEffects.mList.push_back(*effectIt); + magnitudeMult = 0; + } + } + + // Try resisting + if (magnitudeMult > 0 && caster.getClass().isActor()) + { + const ESM::Spell *spell = + MWBase::Environment::get().getWorld()->getStore().get().search (mId); + magnitudeMult = MWMechanics::getEffectMultiplier(effectIt->mEffectID, target, caster, spell); + if (magnitudeMult == 0) + { + // Fully resisted, show message + if (target.getRefData().getHandle() == "player") + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); + else + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}"); + } + } + } + + + if (magnitudeMult > 0) + { + float random = std::rand() / static_cast(RAND_MAX); + float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * random; + magnitude *= magnitudeMult; + + if (target.getClass().isActor() && !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)) + { + ActiveSpells::Effect effect; + effect.mKey = MWMechanics::EffectKey(*effectIt); + effect.mDuration = effectIt->mDuration; + effect.mMagnitude = magnitude; + + appliedLastingEffects.push_back(effect); + } + else + applyInstantEffect(mTarget, effectIt->mEffectID, magnitude); + + if (target.getClass().isActor() || magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) + { + // Play sound, only for the first effect + if (firstAppliedEffect) + { + static const std::string schools[] = { + "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" + }; + + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + if(!magicEffect->mHitSound.empty()) + sndMgr->playSound3D(target, magicEffect->mHitSound, 1.0f, 1.0f); + else + sndMgr->playSound3D(target, schools[magicEffect->mData.mSchool]+" hit", 1.0f, 1.0f); + firstAppliedEffect = false; + } + + // Add VFX + if (!magicEffect->mHit.empty()) + { + const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get().find (magicEffect->mHit); + bool loop = magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx; + // Note: in case of non actor, a free effect should be fine as well + MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(target); + if (anim) + anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, ""); + } + } + + // TODO: For Area effects, launch a growing particle effect that applies the effect to more actors as it hits them. Best managed in World. + } + } + + if (reflectedEffects.mList.size()) + inflict(caster, target, reflectedEffects, range, true); + + if (appliedLastingEffects.size()) + target.getClass().getCreatureStats(target).getActiveSpells().addSpell(mId, mStack, appliedLastingEffects, mSourceName); + } + + void CastSpell::applyInstantEffect(const MWWorld::Ptr &target, short effectId, float magnitude) + { + if (!target.getClass().isActor()) + { + if (effectId == ESM::MagicEffect::Lock) + { + if (target.getCellRef().mLockLevel < magnitude) + target.getCellRef().mLockLevel = magnitude; + } + else if (effectId == ESM::MagicEffect::Open) + { + // TODO: This is a crime + if (target.getCellRef().mLockLevel <= magnitude) + { + if (target.getCellRef().mLockLevel > 0) + MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock", 1.f, 1.f); + target.getCellRef().mLockLevel = 0; + } + else + MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock Fail", 1.f, 1.f); + } + } + else + { + if (effectId == ESM::MagicEffect::CurePoison) + target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect(ESM::MagicEffect::Poison); + else if (effectId == ESM::MagicEffect::CureParalyzation) + target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect(ESM::MagicEffect::Paralyze); + else if (effectId == ESM::MagicEffect::CureCommonDisease) + target.getClass().getCreatureStats(target).getSpells().purgeCommonDisease(); + else if (effectId == ESM::MagicEffect::CureBlightDisease) + target.getClass().getCreatureStats(target).getSpells().purgeBlightDisease(); + else if (effectId == ESM::MagicEffect::CureCorprusDisease) + target.getClass().getCreatureStats(target).getSpells().purgeCorprusDisease(); + else if (effectId == ESM::MagicEffect::Dispel) + target.getClass().getCreatureStats(target).getActiveSpells().purgeAll(); + else if (effectId == ESM::MagicEffect::RemoveCurse) + target.getClass().getCreatureStats(target).getSpells().purgeCurses(); + + else if (effectId == ESM::MagicEffect::DivineIntervention) + { + // We need to be able to get the world location of an interior cell before implementing this + // or alternatively, the last known exterior location of the player, which is how vanilla does it. + } + else if (effectId == ESM::MagicEffect::AlmsiviIntervention) + { + // Same as above + } + + else if (effectId == ESM::MagicEffect::Mark) + { + // TODO + } + else if (effectId == ESM::MagicEffect::Recall) + { + // TODO + } + } + } + + bool CastSpell::cast(const std::string &id) + { + if (const ESM::Spell *spell = + MWBase::Environment::get().getWorld()->getStore().get().search (id)) + return cast(spell); + + if (const ESM::Potion *potion = + MWBase::Environment::get().getWorld()->getStore().get().search (id)) + return cast(potion); + + if (const ESM::Ingredient *ingredient = + MWBase::Environment::get().getWorld()->getStore().get().search (id)) + return cast(ingredient); + + throw std::runtime_error("ID type cannot be casted"); + } + + bool CastSpell::cast(const MWWorld::Ptr &item) + { + std::string enchantmentName = item.getClass().getEnchantment(item); + if (enchantmentName.empty()) + throw std::runtime_error("can't cast an item without an enchantment"); + + mSourceName = item.getClass().getName(item); + mId = item.getCellRef().mRefID; + + const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find(enchantmentName); + + mStack = (enchantment->mData.mType == ESM::Enchantment::CastOnce); + + if (enchantment->mData.mType == ESM::Enchantment::WhenUsed) + { + // Check if there's enough charge left + const float enchantCost = enchantment->mData.mCost; + MWMechanics::NpcStats &stats = MWWorld::Class::get(mCaster).getNpcStats(mCaster); + int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified(); + const float castCost = enchantCost - (enchantCost / 100) * (eSkill - 10); + + if (item.getCellRef().mEnchantmentCharge == -1) + item.getCellRef().mEnchantmentCharge = enchantment->mData.mCharge; + + if (mCaster.getRefData().getHandle() == "player" && item.getCellRef().mEnchantmentCharge < castCost) + { + // TODO: Should there be a sound here? + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientCharge}"); + return false; + } + + // Reduce charge + item.getCellRef().mEnchantmentCharge -= castCost; + } + if (enchantment->mData.mType == ESM::Enchantment::CastOnce) + item.getContainerStore()->remove(item, 1, mCaster); + else + { + if (mCaster.getRefData().getHandle() == "player") + MWBase::Environment::get().getWindowManager()->setSelectedEnchantItem(item); // Set again to show the modified charge + } + + inflict(mCaster, mCaster, enchantment->mEffects, ESM::RT_Self); + + if (!mTarget.isEmpty()) + { + if (!mTarget.getClass().isActor() || !mTarget.getClass().getCreatureStats(mTarget).isDead()) + inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Touch); + } + + MWBase::Environment::get().getWorld()->launchProjectile(mId, enchantment->mEffects, mCaster, mSourceName); + + return true; + } + + bool CastSpell::cast(const ESM::Potion* potion) + { + mSourceName = potion->mName; + mId = potion->mId; + mStack = true; + + inflict(mCaster, mCaster, potion->mEffects, ESM::RT_Self); + + return true; + } + + bool CastSpell::cast(const ESM::Spell* spell) + { + mSourceName = spell->mName; + mId = spell->mId; + mStack = false; + + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + + int school = 0; + + if (mCaster.getClass().isActor()) + { + school = getSpellSchool(spell, mCaster); + + CreatureStats& stats = mCaster.getClass().getCreatureStats(mCaster); + + // Reduce fatigue (note that in the vanilla game, both GMSTs are 0, and there's no fatigue loss) + static const float fFatigueSpellBase = store.get().find("fFatigueSpellBase")->getFloat(); + static const float fFatigueSpellMult = store.get().find("fFatigueSpellMult")->getFloat(); + DynamicStat fatigue = stats.getFatigue(); + const float normalizedEncumbrance = mCaster.getClass().getEncumbrance(mCaster) / mCaster.getClass().getCapacity(mCaster); + float fatigueLoss = spell->mData.mCost * (fFatigueSpellBase + normalizedEncumbrance * fFatigueSpellMult); + fatigue.setCurrent(std::max(0.f, fatigue.getCurrent() - fatigueLoss)); + stats.setFatigue(fatigue); + + // Check mana + bool fail = false; + DynamicStat magicka = stats.getMagicka(); + if (magicka.getCurrent() < spell->mData.mCost) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientSP}"); + fail = true; + } + + // Reduce mana + if (!fail) + { + magicka.setCurrent(magicka.getCurrent() - spell->mData.mCost); + stats.setMagicka(magicka); + } + + // If this is a power, check if it was already used in last 24h + if (!fail && spell->mData.mType & ESM::Spell::ST_Power) + { + if (stats.canUsePower(spell->mId)) + stats.usePower(spell->mId); + else + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sPowerAlreadyUsed}"); + fail = true; + } + } + + // Check success + int successChance = getSpellSuccessChance(spell, mCaster); + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + if (!fail && roll >= successChance) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicSkillFail}"); + fail = true; + } + + if (fail) + { + // Failure sound + static const std::string schools[] = { + "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" + }; + + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + sndMgr->playSound3D(mCaster, "Spell Failure " + schools[school], 1.0f, 1.0f); + return false; + } + } + + if (mCaster.getRefData().getHandle() == "player" && spell->mData.mType == ESM::Spell::ST_Spell) + mCaster.getClass().skillUsageSucceeded(mCaster, + spellSchoolToSkill(school), 0); + + inflict(mCaster, mCaster, spell->mEffects, ESM::RT_Self); + + if (!mTarget.isEmpty()) + { + if (!mTarget.getClass().isActor() || !mTarget.getClass().getCreatureStats(mTarget).isDead()) + { + inflict(mTarget, mCaster, spell->mEffects, ESM::RT_Touch); + } + } + + MWBase::Environment::get().getWorld()->launchProjectile(mId, spell->mEffects, mCaster, mSourceName); + return true; + } + + bool CastSpell::cast (const ESM::Ingredient* ingredient) + { + mId = ingredient->mId; + mStack = true; + mSourceName = ingredient->mName; + + ESM::ENAMstruct effect; + effect.mEffectID = ingredient->mData.mEffectID[0]; + effect.mSkill = ingredient->mData.mSkills[0]; + effect.mAttribute = ingredient->mData.mAttributes[0]; + effect.mRange = ESM::RT_Self; + effect.mArea = 0; + + const ESM::MagicEffect *magicEffect = + MWBase::Environment::get().getWorld()->getStore().get().find ( + effect.mEffectID); + + const MWMechanics::NpcStats& npcStats = mCaster.getClass().getNpcStats(mCaster); + const MWMechanics::CreatureStats& creatureStats = mCaster.getClass().getCreatureStats(mCaster); + + float x = (npcStats.getSkill (ESM::Skill::Alchemy).getModified() + + 0.2 * creatureStats.getAttribute (ESM::Attribute::Intelligence).getModified() + + 0.1 * creatureStats.getAttribute (ESM::Attribute::Luck).getModified()) + * creatureStats.getFatigueTerm(); + + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + if (roll > x) + { + // "X has no effect on you" + std::string message = MWBase::Environment::get().getWorld()->getStore().get().find("#{sNotifyMessage50}")->getString(); + message = boost::str(boost::format(message) % ingredient->mName); + + MWBase::Environment::get().getWindowManager()->messageBox(message); + return false; + } + + float magnitude = 0; + float y = roll / std::min(x, 100.f); + y *= 0.25 * x; + if (magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) + effect.mDuration = int(y); + else + effect.mDuration = 1; + if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)) + { + if (!magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) + magnitude = int((0.05 * y) / (0.1 * magicEffect->mData.mBaseCost)); + else + magnitude = int(y / (0.1 * magicEffect->mData.mBaseCost)); + magnitude = std::max(1.f, magnitude); + } + else + magnitude = 1; + + effect.mMagnMax = magnitude; + effect.mMagnMin = magnitude; + + ESM::EffectList effects; + effects.mList.push_back(effect); + + inflict(mCaster, mCaster, effects, ESM::RT_Self); + + return true; + } + +} diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index 3bbb4d0c53..5e87e19933 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -44,12 +44,6 @@ namespace MWMechanics if (creatureStats.getMagicEffects().get(ESM::MagicEffect::Silence).mMagnitude) return 0; - if (spell->mData.mType != ESM::Spell::ST_Spell) - return 100; - - if (spell->mData.mFlags & ESM::Spell::F_Always) - return 100; - float y = FLT_MAX; float lowestSkill = 0; @@ -79,8 +73,14 @@ namespace MWMechanics } } + if (spell->mData.mType != ESM::Spell::ST_Spell) + return 100; + + if (spell->mData.mFlags & ESM::Spell::F_Always) + return 100; int castBonus = -stats.getMagicEffects().get(ESM::MagicEffect::Sound).mMagnitude; + int actorWillpower = stats.getAttribute(ESM::Attribute::Willpower).getModified(); int actorLuck = stats.getAttribute(ESM::Attribute::Luck).getModified(); @@ -98,7 +98,6 @@ namespace MWMechanics return getSpellSuccessChance(spell, actor, effectiveSchool); } - /// @note this only works for ST_Spell inline int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor) { int school = 0; @@ -106,7 +105,6 @@ namespace MWMechanics return school; } - /// @note this only works for ST_Spell inline int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor) { int school = 0; @@ -180,6 +178,34 @@ namespace MWMechanics return -(resistance-100) / 100.f; } + + class CastSpell + { + private: + MWWorld::Ptr mCaster; + MWWorld::Ptr mTarget; + + bool mStack; + std::string mId; // ID of spell, potion, item etc + std::string mSourceName; // Display name for spell, potion, etc + + public: + CastSpell(const MWWorld::Ptr& caster, const MWWorld::Ptr& target); + + bool cast (const ESM::Spell* spell); + bool cast (const MWWorld::Ptr& item); + bool cast (const ESM::Ingredient* ingredient); + bool cast (const ESM::Potion* potion); + + /// @note Auto detects if spell, ingredient or potion + bool cast (const std::string& id); + + void inflict (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, + const ESM::EffectList& effects, ESM::RangeType range, bool reflected=false); + + void applyInstantEffect (const MWWorld::Ptr& target, short effectId, float magnitude); + }; + } #endif diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index a5a5677d12..5b18e2a3c5 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -118,6 +118,62 @@ namespace MWMechanics return false; } + void Spells::purgeCommonDisease() + { + for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) + { + const ESM::Spell *spell = + MWBase::Environment::get().getWorld()->getStore().get().find (iter->first); + + if (spell->mData.mType & ESM::Spell::ST_Disease) + mSpells.erase(iter++); + else + iter++; + } + } + + void Spells::purgeBlightDisease() + { + for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) + { + const ESM::Spell *spell = + MWBase::Environment::get().getWorld()->getStore().get().find (iter->first); + + if (spell->mData.mType & ESM::Spell::ST_Blight) + mSpells.erase(iter++); + else + iter++; + } + } + + void Spells::purgeCorprusDisease() + { + for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) + { + const ESM::Spell *spell = + MWBase::Environment::get().getWorld()->getStore().get().find (iter->first); + + if (Misc::StringUtils::ciEqual(spell->mId, "corprus")) + mSpells.erase(iter++); + else + iter++; + } + } + + void Spells::purgeCurses() + { + for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) + { + const ESM::Spell *spell = + MWBase::Environment::get().getWorld()->getStore().get().find (iter->first); + + if (spell->mData.mType == ESM::Spell::ST_Curse) + mSpells.erase(iter++); + else + iter++; + } + } + void Spells::visitEffectSources(EffectSourceVisitor &visitor) const { for (TIterator it = begin(); it != end(); ++it) @@ -136,7 +192,7 @@ namespace MWMechanics effectIt != list.mList.end(); ++effectIt, ++i) { float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * it->second[i]; - visitor.visit(*effectIt, spell->mName, magnitude); + visitor.visit(MWMechanics::EffectKey(*effectIt), spell->mName, magnitude); } } } diff --git a/apps/openmw/mwmechanics/spells.hpp b/apps/openmw/mwmechanics/spells.hpp index 79b7a782d0..cf9b660915 100644 --- a/apps/openmw/mwmechanics/spells.hpp +++ b/apps/openmw/mwmechanics/spells.hpp @@ -35,6 +35,11 @@ namespace MWMechanics public: + void purgeCommonDisease(); + void purgeBlightDisease(); + void purgeCorprusDisease(); + void purgeCurses(); + TIterator begin() const; TIterator end() const; diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 10c925b36a..a29beb35c8 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -994,16 +994,27 @@ void Animation::detachObjectFromBone(Ogre::MovableObject *obj) mSkelBase->detachObjectFromBone(obj); } -void Animation::addEffect(const std::string &model, int effectId, bool loop, const std::string &bonename) +void Animation::addEffect(const std::string &model, int effectId, bool loop, const std::string &bonename, std::string texture) { // Early out if we already have this effect for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ++it) if (it->mLoop && loop && it->mEffectId == effectId && it->mBoneName == bonename) return; + // fix texture extension to .dds + if (texture.size() > 4) + { + texture[texture.size()-3] = 'd'; + texture[texture.size()-2] = 'd'; + texture[texture.size()-1] = 's'; + } + EffectParams params; params.mModelName = model; - params.mObjects = NifOgre::Loader::createObjects(mInsert, model); + if (bonename.empty()) + params.mObjects = NifOgre::Loader::createObjects(mInsert, model); + else + params.mObjects = NifOgre::Loader::createObjects(mSkelBase, bonename, mInsert, model); params.mLoop = loop; params.mEffectId = effectId; params.mBoneName = bonename; @@ -1013,6 +1024,35 @@ void Animation::addEffect(const std::string &model, int effectId, bool loop, con if(params.mObjects.mControllers[i].getSource().isNull()) params.mObjects.mControllers[i].setSource(Ogre::SharedPtr (new EffectAnimationValue())); } + + if (!texture.empty()) + { + for(size_t i = 0;i < params.mObjects.mParticles.size(); ++i) + { + Ogre::ParticleSystem* partSys = params.mObjects.mParticles[i]; + Ogre::MaterialPtr mat = Ogre::MaterialManager::getSingleton().getByName(partSys->getMaterialName()); + static int count = 0; + Ogre::String materialName = "openmw/" + Ogre::StringConverter::toString(count++); + // TODO: destroy when effect is removed + Ogre::MaterialPtr newMat = mat->clone(materialName); + partSys->setMaterialName(materialName); + + for (int t=0; tgetNumTechniques(); ++t) + { + Ogre::Technique* tech = newMat->getTechnique(t); + for (int p=0; pgetNumPasses(); ++p) + { + Ogre::Pass* pass = tech->getPass(p); + for (int tex=0; texgetNumTextureUnitStates(); ++tex) + { + Ogre::TextureUnitState* tus = pass->getTextureUnitState(tex); + tus->setTextureName("textures\\" + texture); + } + } + } + } + } + mEffects.push_back(params); } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index b1572b6a16..7d1fd010ca 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -206,9 +206,10 @@ public: * @param loop Loop the effect. If false, it is removed automatically after it finishes playing. If true, * you need to remove it manually using removeEffect when the effect should end. * @param bonename Bone to attach to, or empty string to use the scene node instead + * @param texture override the texture specified in the model's materials * @note Will not add an effect twice. */ - void addEffect (const std::string& model, int effectId, bool loop = false, const std::string& bonename = ""); + void addEffect (const std::string& model, int effectId, bool loop = false, const std::string& bonename = "", std::string texture = ""); void removeEffect (int effectId); void getLoopingEffects (std::vector& out); private: diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index fd81baf6ed..4d5f6872df 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -281,3 +281,11 @@ void Objects::updateObjectCell(const MWWorld::Ptr &old, const MWWorld::Ptr &cur) node->addChild(cur.getRefData().getBaseNode()); } +ObjectAnimation* Objects::getAnimation(const MWWorld::Ptr &ptr) +{ + PtrAnimationMap::const_iterator iter = mObjects.find(ptr); + if(iter != mObjects.end()) + return iter->second; + return NULL; +} + diff --git a/apps/openmw/mwrender/objects.hpp b/apps/openmw/mwrender/objects.hpp index 22dd1e4f5d..165f6551d3 100644 --- a/apps/openmw/mwrender/objects.hpp +++ b/apps/openmw/mwrender/objects.hpp @@ -41,6 +41,8 @@ public: ~Objects(){} void insertModel(const MWWorld::Ptr& ptr, const std::string &model); + ObjectAnimation* getAnimation(const MWWorld::Ptr &ptr); + void enableLights(); void disableLights(); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 57e00d76c7..1b891368ff 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -975,8 +975,13 @@ void RenderingManager::setupExternalRendering (MWRender::ExternalRendering& rend Animation* RenderingManager::getAnimation(const MWWorld::Ptr &ptr) { Animation *anim = mActors.getAnimation(ptr); + if(!anim && ptr.getRefData().getHandle() == "player") anim = mPlayerAnimation; + + if (!anim) + anim = mObjects.getAnimation(ptr); + return anim; } diff --git a/apps/openmw/mwworld/actioneat.cpp b/apps/openmw/mwworld/actioneat.cpp index 470eeda2b3..f5d7e26361 100644 --- a/apps/openmw/mwworld/actioneat.cpp +++ b/apps/openmw/mwworld/actioneat.cpp @@ -3,17 +3,11 @@ #include -#include - #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" -#include "../mwmechanics/creaturestats.hpp" -#include "../mwmechanics/npcstats.hpp" - #include "../mwworld/containerstore.hpp" -#include "esmstore.hpp" #include "class.hpp" namespace MWWorld @@ -23,27 +17,11 @@ namespace MWWorld // remove used item (assume the item is present in inventory) getTarget().getContainerStore()->remove(getTarget(), 1, actor); - // check for success - const MWMechanics::CreatureStats& creatureStats = MWWorld::Class::get (actor).getCreatureStats (actor); - MWMechanics::NpcStats& npcStats = MWWorld::Class::get (actor).getNpcStats (actor); - - float x = - (npcStats.getSkill (ESM::Skill::Alchemy).getModified() + - 0.2 * creatureStats.getAttribute (1).getModified() - + 0.1 * creatureStats.getAttribute (7).getModified()) - * creatureStats.getFatigueTerm(); - - if (x>=100*static_cast (std::rand()) / RAND_MAX) - { - // apply to actor - std::string id = Class::get (getTarget()).getId (getTarget()); + // apply to actor + std::string id = Class::get (getTarget()).getId (getTarget()); - Class::get (actor).apply (actor, id, actor); - // we ignore the result here. Skill increases no matter if the ingredient did something or not. - - // increase skill + if (Class::get (actor).apply (actor, id, actor)) Class::get (actor).skillUsageSucceeded (actor, ESM::Skill::Alchemy, 1); - } } ActionEat::ActionEat (const MWWorld::Ptr& object) : Action (false, object) {} diff --git a/apps/openmw/mwworld/actiontrap.cpp b/apps/openmw/mwworld/actiontrap.cpp index 80da290727..d723b98239 100644 --- a/apps/openmw/mwworld/actiontrap.cpp +++ b/apps/openmw/mwworld/actiontrap.cpp @@ -1,26 +1,14 @@ #include "actiontrap.hpp" -#include "../mwworld/class.hpp" - -#include "../mwmechanics/activespells.hpp" -#include "../mwmechanics/creaturestats.hpp" - -#include "../mwbase/world.hpp" -#include "../mwbase/environment.hpp" +#include "../mwmechanics/spellcasting.hpp" namespace MWWorld { void ActionTrap::executeImp(const Ptr &actor) { - // TODO: Apply RT_Self effects on the door / container that triggered the trap. Not terribly useful, but you could - // make it lock itself when activated for example. - - actor.getClass().getCreatureStats(actor).getActiveSpells().addSpell(mSpellId, actor, actor, ESM::RT_Touch); - - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(mSpellId); - - MWBase::Environment::get().getWorld()->launchProjectile(mSpellId, spell->mEffects, mTrapSource, spell->mName); + MWMechanics::CastSpell cast(mTrapSource, actor); + cast.cast(mSpellId); mTrapSource.getCellRef().mTrap = ""; } diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 72523a69ce..6483b32b2e 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -9,6 +9,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/spellcasting.hpp" @@ -339,6 +340,9 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) if (params[i].mMultiplier == 0) continue; + float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params[i].mRandom; + magnitude *= params[i].mMultiplier; + if (!existed) { // During first auto equip, we don't play any sounds. @@ -346,10 +350,13 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) // the items should appear as if they'd always been equipped. mListener->permanentEffectAdded(magicEffect, !mFirstAutoEquip, !mFirstAutoEquip && effectIt == enchantment.mEffects.mList.begin()); + + // Apply instant effects + MWMechanics::CastSpell cast(actor, actor); + if (magnitude) + cast.applyInstantEffect(actor, effectIt->mEffectID, magnitude); } - float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params[i].mRandom; - magnitude *= params[i].mMultiplier; if (magnitude) mMagicEffects.add (*effectIt, magnitude); } @@ -376,6 +383,9 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) ++it; } + // Magic effects are normally not updated when paused, but we need this to make resistances work immediately after equipping + MWBase::Environment::get().getMechanicsManager()->updateMagicEffects(actor); + mFirstAutoEquip = false; } @@ -442,6 +452,13 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor autoEquip(actor); } + if (item.getRefData().getCount() == 0 && mSelectedEnchantItem != end() + && *mSelectedEnchantItem == item && actor.getRefData().getHandle() == "player") + { + mSelectedEnchantItem = end(); + MWBase::Environment::get().getWindowManager()->unsetSelectedSpell(); + } + return retCount; } @@ -554,7 +571,7 @@ void MWWorld::InventoryStore::visitEffectSources(MWMechanics::EffectSourceVisito const EffectParams& params = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID][i]; float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params.mRandom; magnitude *= params.mMultiplier; - visitor.visit(*effectIt, (**iter).getClass().getName(**iter), magnitude); + visitor.visit(MWMechanics::EffectKey(*effectIt), (**iter).getClass().getName(**iter), magnitude); ++i; } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 2dd0a9b930..7dec848ad3 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2029,156 +2029,28 @@ namespace MWWorld void World::castSpell(const Ptr &actor) { MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); + InventoryStore& inv = actor.getClass().getInventoryStore(actor); + + // Unset casting flag, otherwise pressing the mouse button down would continue casting every frame if using an enchantment + // (which casts instantly without an animation) stats.setAttackingOrSpell(false); - ESM::EffectList effects; + MWWorld::Ptr target = getFacedObject(); std::string selectedSpell = stats.getSpells().getSelectedSpell(); - std::string sourceName; + + MWMechanics::CastSpell cast(actor, target); + if (!selectedSpell.empty()) { const ESM::Spell* spell = getStore().get().search(selectedSpell); - // Reduce fatigue (note that in the vanilla game, both GMSTs are 0, and there's no fatigue loss) - static const float fFatigueSpellBase = getStore().get().find("fFatigueSpellBase")->getFloat(); - static const float fFatigueSpellMult = getStore().get().find("fFatigueSpellMult")->getFloat(); - MWMechanics::DynamicStat fatigue = stats.getFatigue(); - const float normalizedEncumbrance = actor.getClass().getEncumbrance(actor) / actor.getClass().getCapacity(actor); - float fatigueLoss = spell->mData.mCost * (fFatigueSpellBase + normalizedEncumbrance * fFatigueSpellMult); - fatigue.setCurrent(std::max(0.f, fatigue.getCurrent() - fatigueLoss)); - stats.setFatigue(fatigue); - - // Check mana - bool fail = false; - MWMechanics::DynamicStat magicka = stats.getMagicka(); - if (magicka.getCurrent() < spell->mData.mCost) - { - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientSP}"); - fail = true; - } - - // Reduce mana - if (!fail) - { - magicka.setCurrent(magicka.getCurrent() - spell->mData.mCost); - stats.setMagicka(magicka); - } - - // If this is a power, check if it was already used in last 24h - if (!fail && spell->mData.mType & ESM::Spell::ST_Power) - { - if (stats.canUsePower(selectedSpell)) - stats.usePower(selectedSpell); - else - { - MWBase::Environment::get().getWindowManager()->messageBox("#{sPowerAlreadyUsed}"); - fail = true; - } - } - - // Check success - int successChance = MWMechanics::getSpellSuccessChance(selectedSpell, actor); - int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] - if (!fail && roll >= successChance) - { - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicSkillFail}"); - fail = true; - } - - if (fail) - { - // Failure sound - for (std::vector::const_iterator iter (spell->mEffects.mList.begin()); - iter!=spell->mEffects.mList.end(); ++iter) - { - const ESM::MagicEffect *magicEffect = getStore().get().find ( - iter->mEffectID); - - static const std::string schools[] = { - "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" - }; - - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - sndMgr->playSound3D(actor, "Spell Failure " + schools[magicEffect->mData.mSchool], 1.0f, 1.0f); - break; - } - return; - } - - if (actor == getPlayer().getPlayer() && spell->mData.mType == ESM::Spell::ST_Spell) - actor.getClass().skillUsageSucceeded(actor, - MWMechanics::spellSchoolToSkill(MWMechanics::getSpellSchool(selectedSpell, actor)), 0); - - effects = spell->mEffects; + cast.cast(spell); } - InventoryStore& inv = actor.getClass().getInventoryStore(actor); - if (selectedSpell.empty() && inv.getSelectedEnchantItem() != inv.end()) + else if (inv.getSelectedEnchantItem() != inv.end()) { - MWWorld::Ptr item = *inv.getSelectedEnchantItem(); - selectedSpell = item.getClass().getEnchantment(item); - const ESM::Enchantment* enchantment = getStore().get().search (selectedSpell); - - if (enchantment->mData.mType == ESM::Enchantment::WhenUsed) - { - // Check if there's enough charge left - const float enchantCost = enchantment->mData.mCost; - MWMechanics::NpcStats &stats = MWWorld::Class::get(actor).getNpcStats(actor); - int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified(); - const float castCost = enchantCost - (enchantCost / 100) * (eSkill - 10); - - if (item.getCellRef().mEnchantmentCharge == -1) - item.getCellRef().mEnchantmentCharge = enchantment->mData.mCharge; - - if (item.getCellRef().mEnchantmentCharge < castCost) - { - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientCharge}"); - return; - } - - // Reduce charge - item.getCellRef().mEnchantmentCharge -= castCost; - } - if (enchantment->mData.mType == ESM::Enchantment::CastOnce) - { - if (!item.getContainerStore()->remove(item, 1, actor)) - { - // Item was used up - MWBase::Environment::get().getWindowManager()->unsetSelectedSpell(); - inv.setSelectedEnchantItem(inv.end()); - } - } - else - MWBase::Environment::get().getWindowManager()->setSelectedEnchantItem(item); // Set again to show the modified charge - - sourceName = item.getClass().getName(item); - - effects = enchantment->mEffects; + cast.cast(*inv.getSelectedEnchantItem()); } - - // Now apply the spell! - - // Apply Self portion - actor.getClass().getCreatureStats(actor).getActiveSpells().addSpell(selectedSpell, actor, actor, ESM::RT_Self, sourceName); - - // Apply Touch portion - // TODO: Distance is probably incorrect, and should it be hardcoded? - std::pair contact = getHitContact(actor, 100); - if (!contact.first.isEmpty()) - { - if (contact.first.getClass().isActor()) - { - if (!contact.first.getClass().getCreatureStats(contact.first).isDead()) - contact.first.getClass().getCreatureStats(contact.first).getActiveSpells().addSpell(selectedSpell, contact.first, actor, ESM::RT_Touch, sourceName); - } - else - { - // We hit a non-actor, e.g. a door. Only instant effects are relevant. - // inflictSpellOnNonActor(contact.first, selectedSpell, ESM::RT_Touch); - } - } - - launchProjectile(selectedSpell, effects, actor, sourceName); - } void World::launchProjectile (const std::string& id, const ESM::EffectList& effects, From a42069823734b4ddd162e6e6740cccb539826a25 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 17 Nov 2013 23:34:52 +0100 Subject: [PATCH 304/434] Don't try to absorb or reflect for self casted spells --- apps/openmw/mwmechanics/spellcasting.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 00ca82c5af..1498a34aac 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -59,7 +59,8 @@ namespace MWMechanics // Try absorbing if it's a spell // NOTE: Vanilla does this once per effect source instead of adding the % from all sources together, not sure // if that is worth replicating. - if (const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search (mId)) + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search (mId); + if (spell && caster != target) { int absorb = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).mMagnitude; int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] @@ -78,7 +79,7 @@ namespace MWMechanics } // Try reflecting - if (!reflected && magnitudeMult > 0) + if (!reflected && magnitudeMult > 0 && caster != target) { int reflect = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Reflect).mMagnitude; int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] @@ -434,9 +435,8 @@ namespace MWMechanics if (roll > x) { // "X has no effect on you" - std::string message = MWBase::Environment::get().getWorld()->getStore().get().find("#{sNotifyMessage50}")->getString(); + std::string message = MWBase::Environment::get().getWorld()->getStore().get().find("sNotifyMessage50")->getString(); message = boost::str(boost::format(message) % ingredient->mName); - MWBase::Environment::get().getWindowManager()->messageBox(message); return false; } From 6902569d035bf87afd8b0336f9c2486b3e9ff9d4 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 18 Nov 2013 04:57:54 +0100 Subject: [PATCH 305/434] Implement Absorb effects (AbsorbHealth, etc) --- apps/openmw/mwmechanics/actors.cpp | 9 ++++++--- apps/openmw/mwmechanics/spellcasting.cpp | 16 +++++++++++++++- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 6d31a810aa..6c559f0946 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -155,7 +155,8 @@ namespace MWMechanics { Stat stat = creatureStats.getAttribute(i); stat.setModifier(effects.get(EffectKey(ESM::MagicEffect::FortifyAttribute, i)).mMagnitude - - effects.get(EffectKey(ESM::MagicEffect::DrainAttribute, i)).mMagnitude); + effects.get(EffectKey(ESM::MagicEffect::DrainAttribute, i)).mMagnitude - + effects.get(EffectKey(ESM::MagicEffect::AbsorbAttribute, i)).mMagnitude); creatureStats.setAttribute(i, stat); } @@ -168,7 +169,8 @@ namespace MWMechanics effects.get(EffectKey(ESM::MagicEffect::DrainHealth+i)).mMagnitude); float currentDiff = creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::RestoreHealth+i)).mMagnitude - - creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::DamageHealth+i)).mMagnitude; + - creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::DamageHealth+i)).mMagnitude + - creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::AbsorbHealth)).mMagnitude; stat.setCurrent(stat.getCurrent() + currentDiff * duration); creatureStats.setDynamic(i, stat); @@ -198,7 +200,8 @@ namespace MWMechanics { Stat& skill = npcStats.getSkill(i); skill.setModifier(effects.get(EffectKey(ESM::MagicEffect::FortifySkill, i)).mMagnitude - - effects.get(EffectKey(ESM::MagicEffect::DrainSkill, i)).mMagnitude); + effects.get(EffectKey(ESM::MagicEffect::DrainSkill, i)).mMagnitude - + effects.get(EffectKey(ESM::MagicEffect::AbsorbSkill, i)).mMagnitude); } } diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 1498a34aac..5763549cf5 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -116,7 +116,7 @@ namespace MWMechanics { float random = std::rand() / static_cast(RAND_MAX); float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * random; - magnitude *= magnitudeMult; + magnitude *= magnitudeMult; if (target.getClass().isActor() && !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)) { @@ -126,6 +126,20 @@ namespace MWMechanics effect.mMagnitude = magnitude; appliedLastingEffects.push_back(effect); + + // For absorb effects, also apply the effect to the caster - but with a negative + // magnitude, since we're transfering stats from the target to the caster + for (int i=0; i<5; ++i) + { + if (effectIt->mEffectID == ESM::MagicEffect::AbsorbAttribute+i) + { + std::vector effects; + ActiveSpells::Effect effect_ = effect; + effect_.mMagnitude *= -1; + effects.push_back(effect_); + caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell("", true, effects, mSourceName); + } + } } else applyInstantEffect(mTarget, effectIt->mEffectID, magnitude); From 4559e932ae0b9d1319b6d0fc99396c819a51dfb7 Mon Sep 17 00:00:00 2001 From: gus Date: Mon, 18 Nov 2013 12:33:09 +0100 Subject: [PATCH 306/434] AI packages have priority and combat is triggered in actor.cpp using the Hostile setting in CreaturesState --- apps/openmw/mwmechanics/actors.cpp | 38 +++++++ apps/openmw/mwmechanics/aicombat.cpp | 141 +++++++++++++------------ apps/openmw/mwmechanics/aicombat.hpp | 2 + apps/openmw/mwmechanics/aipackage.hpp | 3 + apps/openmw/mwmechanics/aisequence.cpp | 68 +++--------- apps/openmw/mwmechanics/aisequence.hpp | 6 +- 6 files changed, 135 insertions(+), 123 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 03a161e86a..cbd369a814 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -22,6 +22,11 @@ #include "creaturestats.hpp" #include "movement.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" + +#include "aicombat.hpp" + namespace MWMechanics { void Actors::updateActor (const MWWorld::Ptr& ptr, float duration) @@ -35,6 +40,39 @@ namespace MWMechanics { // AI CreatureStats& creatureStats = MWWorld::Class::get (ptr).getCreatureStats (ptr); + + //engage combat or not? + if(ptr != MWBase::Environment::get().getWorld()->getPlayer().getPlayer() && !creatureStats.isHostile()) + { + ESM::Position playerpos = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getRefData().getPosition(); + ESM::Position actorpos = ptr.getRefData().getPosition(); + float d = sqrt((actorpos.pos[0] - playerpos.pos[0])*(actorpos.pos[0] - playerpos.pos[0]) + +(actorpos.pos[1] - playerpos.pos[1])*(actorpos.pos[1] - playerpos.pos[1]) + +(actorpos.pos[2] - playerpos.pos[2])*(actorpos.pos[2] - playerpos.pos[2])); + float fight = ptr.getClass().getCreatureStats(ptr).getAiSetting(1); + float disp = 100; //creatures don't have disposition, so set it to 100 by default + if(ptr.getTypeName() == typeid(ESM::NPC).name()) + { + disp = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(ptr); + } + bool LOS = MWBase::Environment::get().getWorld()->getLOS(ptr,MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); + if( ( (fight == 100 ) + || (fight >= 95 && d <= 3000) + || (fight >= 90 && d <= 2000) + || (fight >= 80 && d <= 1000) + || (fight >= 80 && disp <= 40) + || (fight >= 70 && disp <= 35 && d <= 1000) + || (fight >= 60 && disp <= 30 && d <= 1000) + || (fight >= 50 && disp == 0) + || (fight >= 40 && disp <= 10 && d <= 500) ) + && LOS + ) + { + creatureStats.getAiSequence().stack(AiCombat("player")); + creatureStats.setHostile(true); + } + } + creatureStats.getAiSequence().execute (ptr,duration); // fatigue restoration diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index aeddb4781e..39a97df6c1 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -34,103 +34,110 @@ namespace MWMechanics bool AiCombat::execute (const MWWorld::Ptr& actor,float duration) { + if(!MWWorld::Class::get(actor).getCreatureStats(actor).isHostile()) return true; + const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr(mTargetId, false); if(MWWorld::Class::get(actor).getCreatureStats(actor).getHealth().getCurrent() <= 0) return true; if(actor.getTypeName() == typeid(ESM::NPC).name()) { + MWWorld::Class::get(actor). MWWorld::Class::get(actor).setStance(actor, MWWorld::Class::Run,true); MWMechanics::DrawState_ state = MWWorld::Class::get(actor).getNpcStats(actor).getDrawState(); if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing) MWWorld::Class::get(actor).getNpcStats(actor).setDrawState(MWMechanics::DrawState_Weapon); //MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(true); + } + ESM::Position pos = actor.getRefData().getPosition(); + const ESM::Pathgrid *pathgrid = + MWBase::Environment::get().getWorld()->getStore().get().search(*actor.getCell()->mCell); - ESM::Position pos = actor.getRefData().getPosition(); - const ESM::Pathgrid *pathgrid = - MWBase::Environment::get().getWorld()->getStore().get().search(*actor.getCell()->mCell); + float xCell = 0; + float yCell = 0; - float xCell = 0; - float yCell = 0; + if (actor.getCell()->mCell->isExterior()) + { + xCell = actor.getCell()->mCell->mData.mX * ESM::Land::REAL_SIZE; + yCell = actor.getCell()->mCell->mData.mY * ESM::Land::REAL_SIZE; + } - if (actor.getCell()->mCell->isExterior()) + ESM::Pathgrid::Point dest; + dest.mX = target.getRefData().getPosition().pos[0]; + dest.mY = target.getRefData().getPosition().pos[1]; + dest.mZ = target.getRefData().getPosition().pos[2]; + + ESM::Pathgrid::Point start; + start.mX = pos.pos[0]; + start.mY = pos.pos[1]; + start.mZ = pos.pos[2]; + + mTimer2 = mTimer2 + duration; + + if(!mPathFinder.isPathConstructed()) + mPathFinder.buildPath(start, dest, pathgrid, xCell, yCell, true); + else + { + mPathFinder2.buildPath(start, dest, pathgrid, xCell, yCell, true); + ESM::Pathgrid::Point lastPt = mPathFinder.getPath().back(); + if((mTimer2 > 0.25)&&(mPathFinder2.getPathSize() < mPathFinder.getPathSize() || + (dest.mX - lastPt.mX)*(dest.mX - lastPt.mX)+(dest.mY - lastPt.mY)*(dest.mY - lastPt.mY)+(dest.mZ - lastPt.mZ)*(dest.mZ - lastPt.mZ) > 200*200)) { - xCell = actor.getCell()->mCell->mData.mX * ESM::Land::REAL_SIZE; - yCell = actor.getCell()->mCell->mData.mY * ESM::Land::REAL_SIZE; + mTimer2 = 0; + mPathFinder = mPathFinder2; } + } - ESM::Pathgrid::Point dest; - dest.mX = target.getRefData().getPosition().pos[0]; - dest.mY = target.getRefData().getPosition().pos[1]; - dest.mZ = target.getRefData().getPosition().pos[2]; + mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2]); - ESM::Pathgrid::Point start; - start.mX = pos.pos[0]; - start.mY = pos.pos[1]; - start.mZ = pos.pos[2]; + float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); + MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false); + MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; - mTimer2 = mTimer2 + duration; - if(!mPathFinder.isPathConstructed()) - mPathFinder.buildPath(start, dest, pathgrid, xCell, yCell, true); + float range = 100; + MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(false); + if((dest.mX - start.mX)*(dest.mX - start.mX)+(dest.mY - start.mY)*(dest.mY - start.mY)+(dest.mZ - start.mZ)*(dest.mZ - start.mZ) + < range*range) + { + float directionX = dest.mX - start.mX; + float directionY = dest.mY - start.mY; + float directionResult = sqrt(directionX * directionX + directionY * directionY); + + zAngle = Ogre::Radian( acos(directionY / directionResult) * sgn(asin(directionX / directionResult)) ).valueDegrees(); + MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false); + + mPathFinder.clearPath(); + + if(mTimer == 0) + { + MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(false); + //mTimer = mTimer + duration; + } + if( mTimer > 1) + { + MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(true); + mTimer = 0; + } else { - mPathFinder2.buildPath(start, dest, pathgrid, xCell, yCell, true); - ESM::Pathgrid::Point lastPt = mPathFinder.getPath().back(); - if((mTimer2 > 0.25)&&(mPathFinder2.getPathSize() < mPathFinder.getPathSize() || - (dest.mX - lastPt.mX)*(dest.mX - lastPt.mX)+(dest.mY - lastPt.mY)*(dest.mY - lastPt.mY)+(dest.mZ - lastPt.mZ)*(dest.mZ - lastPt.mZ) > 200*200)) - { - mTimer2 = 0; - mPathFinder = mPathFinder2; - } + mTimer = mTimer + duration; } - - mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2]); - float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); - MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false); - MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; - - - float range = 100; - MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(false); - if((dest.mX - start.mX)*(dest.mX - start.mX)+(dest.mY - start.mY)*(dest.mY - start.mY)+(dest.mZ - start.mZ)*(dest.mZ - start.mZ) - < range*range) - { - float directionX = dest.mX - start.mX; - float directionY = dest.mY - start.mY; - float directionResult = sqrt(directionX * directionX + directionY * directionY); - - zAngle = Ogre::Radian( acos(directionY / directionResult) * sgn(asin(directionX / directionResult)) ).valueDegrees(); - MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false); - - mPathFinder.clearPath(); - - if(mTimer == 0) - { - MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(false); - //mTimer = mTimer + duration; - } - if( mTimer > 1) - { - MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(true); - mTimer = 0; - } - else - { - mTimer = mTimer + duration; - } - - MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0; - //MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(!MWWorld::Class::get(actor).getCreatureStats(actor).getAttackingOrSpell()); - } + MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0; + //MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(!MWWorld::Class::get(actor).getCreatureStats(actor).getAttackingOrSpell()); } return false; } int AiCombat::getTypeId() const { - return 2; + return 5; + } + + unsigned int AiCombat::getPriority() const + { + return 1; } AiCombat *MWMechanics::AiCombat::clone() const diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index eed92ef3a7..fa71e261fc 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -21,6 +21,8 @@ namespace MWMechanics virtual int getTypeId() const; + virtual unsigned int getPriority() const; + private: std::string mTargetId; diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 794708f227..5832198dad 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -22,6 +22,9 @@ namespace MWMechanics virtual int getTypeId() const = 0; ///< 0: Wanter, 1 Travel, 2 Escort, 3 Follow, 4 Activate + + virtual unsigned int getPriority() const {return 0;} + ///< higher number is higher priority (0 beeing the lowest) }; } diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 994a7a9323..d5fb76eded 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -17,19 +17,14 @@ #include "../mwbase/world.hpp" #include "../mwworld/player.hpp" -#include "../mwbase/mechanicsmanager.hpp" - void MWMechanics::AiSequence::copy (const AiSequence& sequence) { for (std::list::const_iterator iter (sequence.mPackages.begin()); iter!=sequence.mPackages.end(); ++iter) mPackages.push_back ((*iter)->clone()); - mCombat = sequence.mCombat; - mCombatPackage = 0; - if(sequence.mCombat) mCombatPackage = sequence.mCombatPackage->clone(); } -MWMechanics::AiSequence::AiSequence() : mDone (false), mCombat (false), mCombatPackage (0) {} +MWMechanics::AiSequence::AiSequence() : mDone (false) {} MWMechanics::AiSequence::AiSequence (const AiSequence& sequence) : mDone (false) { @@ -69,48 +64,15 @@ void MWMechanics::AiSequence::execute (const MWWorld::Ptr& actor,float duration) { if(actor != MWBase::Environment::get().getWorld()->getPlayer().getPlayer()) { - if(mCombat) + if (!mPackages.empty()) { - mCombatPackage->execute(actor,duration); - } - else - { - if(actor.getTypeName() == typeid(ESM::NPC).name()) + if (mPackages.front()->execute (actor,duration)) { - ESM::Position playerpos = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getRefData().getPosition(); - ESM::Position actorpos = actor.getRefData().getPosition(); - float d = sqrt((actorpos.pos[0] - playerpos.pos[0])*(actorpos.pos[0] - playerpos.pos[0]) - +(actorpos.pos[1] - playerpos.pos[1])*(actorpos.pos[1] - playerpos.pos[1]) - +(actorpos.pos[2] - playerpos.pos[2])*(actorpos.pos[2] - playerpos.pos[2])); - float fight = actor.getClass().getCreatureStats(actor).getAiSetting(1); - float disp = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(actor); - bool LOS = MWBase::Environment::get().getWorld()->getLOS(actor,MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); - if( ( (fight == 100 ) - || (fight >= 95 && d <= 3000) - || (fight >= 90 && d <= 2000) - || (fight >= 80 && d <= 1000) - || (fight >= 80 && disp <= 40) - || (fight >= 70 && disp <= 35 && d <= 1000) - || (fight >= 60 && disp <= 30 && d <= 1000) - || (fight >= 50 && disp == 0) - || (fight >= 40 && disp <= 10 && d <= 500) ) - && LOS - ) - { - mCombat = true; - mCombatPackage = new AiCombat("player"); - } - } - if (!mPackages.empty()) - { - if (mPackages.front()->execute (actor,duration)) - { - mPackages.erase (mPackages.begin()); - mDone = true; - } - else - mDone = false; + mPackages.erase (mPackages.begin()); + mDone = true; } + else + mDone = false; } } } @@ -119,18 +81,20 @@ void MWMechanics::AiSequence::clear() { for (std::list::const_iterator iter (mPackages.begin()); iter!=mPackages.end(); ++iter) delete *iter; - - if(mCombatPackage) - { - delete mCombatPackage; - mCombatPackage = 0; - } + mPackages.clear(); } void MWMechanics::AiSequence::stack (const AiPackage& package) { - mPackages.push_front (package.clone()); + for(std::list::iterator it = mPackages.begin(); it != mPackages.end(); it++) + { + if(mPackages.front()->getPriority() <= package.getPriority()) + mPackages.insert(it,package.clone()); + } + + if(mPackages.empty()) + mPackages.push_front (package.clone()); } void MWMechanics::AiSequence::queue (const AiPackage& package) diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index ea2a048f16..0976ef0995 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -18,8 +18,6 @@ namespace MWMechanics class AiSequence { std::list mPackages; - AiPackage* mCombatPackage; - bool mCombat; bool mDone; @@ -36,7 +34,7 @@ namespace MWMechanics virtual ~AiSequence(); int getTypeId() const; - ///< -1: None, 0: Wanter, 1 Travel, 2 Escort, 3 Follow, 4 Activate + ///< -1: None, 0: Wanter, 1 Travel, 2 Escort, 3 Follow, 4 Activate, 5 Combat bool isPackageDone() const; ///< Has a package been completed during the last update? @@ -46,7 +44,7 @@ namespace MWMechanics void clear(); ///< Remove all packages. - + void stack (const AiPackage& package); ///< Add \a package to the front of the sequence (suspends current package) From 4baaf9463e70d55213af5b82baaad3f1279118df Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Mon, 18 Nov 2013 12:34:25 +0100 Subject: [PATCH 307/434] Remove crashcatcher.cpp from windows builds, it can't be built or used there. --- apps/openmw/CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index a36c179968..4469de85ec 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -6,8 +6,10 @@ configure_file ("${CMAKE_CURRENT_SOURCE_DIR}/config.hpp.cmake" "${CMAKE_CURRENT_ set(GAME main.cpp engine.cpp - crashcatcher.cpp ) +if(NOT WIN32) + set(GAME ${GAME} crashcatcher.cpp) +endif() set(GAME_HEADER engine.hpp config.hpp From dff3cf162dc415ee7d065b8b5f695918b9aad841 Mon Sep 17 00:00:00 2001 From: gus Date: Mon, 18 Nov 2013 23:03:44 +0100 Subject: [PATCH 308/434] ToggleAI script instruction --- apps/openmw/mwbase/mechanicsmanager.hpp | 3 + apps/openmw/mwmechanics/actors.cpp | 68 ++++++++++--------- .../mwmechanics/mechanicsmanagerimp.cpp | 12 +++- .../mwmechanics/mechanicsmanagerimp.hpp | 4 ++ apps/openmw/mwscript/aiextensions.cpp | 15 ++++ apps/openmw/mwscript/docs/vmformat.txt | 4 +- components/compiler/extensions0.cpp | 1 + components/compiler/opcodes.hpp | 2 + 8 files changed, 76 insertions(+), 33 deletions(-) diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 7e09f9b4d7..a3a60d96ec 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -114,6 +114,9 @@ namespace MWBase /// references that are currently not in the scene should be ignored. virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) = 0; + + virtual void toggleAI() = 0; + virtual bool isAIActive() = 0; }; } diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index cbd369a814..bfb0379308 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -39,42 +39,48 @@ namespace MWMechanics if(!MWBase::Environment::get().getWindowManager()->isGuiMode()) { // AI - CreatureStats& creatureStats = MWWorld::Class::get (ptr).getCreatureStats (ptr); - - //engage combat or not? - if(ptr != MWBase::Environment::get().getWorld()->getPlayer().getPlayer() && !creatureStats.isHostile()) + if(MWBase::Environment::get().getMechanicsManager())//check MechanismsManager is already created { - ESM::Position playerpos = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getRefData().getPosition(); - ESM::Position actorpos = ptr.getRefData().getPosition(); - float d = sqrt((actorpos.pos[0] - playerpos.pos[0])*(actorpos.pos[0] - playerpos.pos[0]) - +(actorpos.pos[1] - playerpos.pos[1])*(actorpos.pos[1] - playerpos.pos[1]) - +(actorpos.pos[2] - playerpos.pos[2])*(actorpos.pos[2] - playerpos.pos[2])); - float fight = ptr.getClass().getCreatureStats(ptr).getAiSetting(1); - float disp = 100; //creatures don't have disposition, so set it to 100 by default - if(ptr.getTypeName() == typeid(ESM::NPC).name()) + if(MWBase::Environment::get().getMechanicsManager()->isAIActive())//MWBase::Environment::get().getMechanicsManager()->isAIActive()) { - disp = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(ptr); - } - bool LOS = MWBase::Environment::get().getWorld()->getLOS(ptr,MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); - if( ( (fight == 100 ) - || (fight >= 95 && d <= 3000) - || (fight >= 90 && d <= 2000) - || (fight >= 80 && d <= 1000) - || (fight >= 80 && disp <= 40) - || (fight >= 70 && disp <= 35 && d <= 1000) - || (fight >= 60 && disp <= 30 && d <= 1000) - || (fight >= 50 && disp == 0) - || (fight >= 40 && disp <= 10 && d <= 500) ) - && LOS - ) - { - creatureStats.getAiSequence().stack(AiCombat("player")); - creatureStats.setHostile(true); + CreatureStats& creatureStats = MWWorld::Class::get (ptr).getCreatureStats (ptr); + if(ptr != MWBase::Environment::get().getWorld()->getPlayer().getPlayer()) MWBase::Environment::get().getMechanicsManager()->restoreDynamicStats(); + //engage combat or not? + if(ptr != MWBase::Environment::get().getWorld()->getPlayer().getPlayer() && !creatureStats.isHostile()) + { + ESM::Position playerpos = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getRefData().getPosition(); + ESM::Position actorpos = ptr.getRefData().getPosition(); + float d = sqrt((actorpos.pos[0] - playerpos.pos[0])*(actorpos.pos[0] - playerpos.pos[0]) + +(actorpos.pos[1] - playerpos.pos[1])*(actorpos.pos[1] - playerpos.pos[1]) + +(actorpos.pos[2] - playerpos.pos[2])*(actorpos.pos[2] - playerpos.pos[2])); + float fight = ptr.getClass().getCreatureStats(ptr).getAiSetting(1); + float disp = 100; //creatures don't have disposition, so set it to 100 by default + if(ptr.getTypeName() == typeid(ESM::NPC).name()) + { + disp = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(ptr); + } + bool LOS = MWBase::Environment::get().getWorld()->getLOS(ptr,MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); + if( ( (fight == 100 ) + || (fight >= 95 && d <= 3000) + || (fight >= 90 && d <= 2000) + || (fight >= 80 && d <= 1000) + || (fight >= 80 && disp <= 40) + || (fight >= 70 && disp <= 35 && d <= 1000) + || (fight >= 60 && disp <= 30 && d <= 1000) + || (fight >= 50 && disp == 0) + || (fight >= 40 && disp <= 10 && d <= 500) ) + && LOS + ) + { + creatureStats.getAiSequence().stack(AiCombat("player")); + creatureStats.setHostile(true); + } + } + + creatureStats.getAiSequence().execute (ptr,duration); } } - creatureStats.getAiSequence().execute (ptr,duration); - // fatigue restoration calculateRestoration(ptr, duration); } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 8c13db7900..cfac6541b7 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -165,7 +165,7 @@ namespace MWMechanics MechanicsManager::MechanicsManager() : mUpdatePlayer (true), mClassSelected (false), - mRaceSelected (false) + mRaceSelected (false), mAI(true) { buildPlayer(); } @@ -679,4 +679,14 @@ namespace MWMechanics return false; } + void MechanicsManager::toggleAI() + { + mAI = !mAI; + } + + bool MechanicsManager::isAIActive() + { + return mAI; + } + } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index ad07562c7b..2cb84bd655 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -29,6 +29,7 @@ namespace MWMechanics bool mUpdatePlayer; bool mClassSelected; bool mRaceSelected; + bool mAI;///< is AI active? Objects mObjects; Actors mActors; @@ -100,6 +101,9 @@ namespace MWMechanics virtual void playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number); virtual void skipAnimation(const MWWorld::Ptr& ptr); virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string &groupName); + + virtual void toggleAI(); + virtual bool isAIActive(); }; } diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index 39ed36ac7c..1cdbaa008d 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -25,6 +25,8 @@ #include +#include "../mwbase/mechanicsmanager.hpp" + namespace MWScript { namespace Ai @@ -390,6 +392,17 @@ namespace MWScript } }; + template + class OpToggleAI : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWBase::Environment::get().getMechanicsManager()->toggleAI(); + } + }; + void installOpcodes (Interpreter::Interpreter& interpreter) { interpreter.installSegment3 (Compiler::Ai::opcodeAIActivate, new OpAiActivate); @@ -416,6 +429,8 @@ namespace MWScript interpreter.installSegment3 (Compiler::Ai::opcodeGetDetectedExplicit, new OpGetDetected); interpreter.installSegment5 (Compiler::Ai::opcodeGetLineOfSight, new OpGetLineOfSight); interpreter.installSegment5 (Compiler::Ai::opcodeGetLineOfSightExplicit, new OpGetLineOfSight); + interpreter.installSegment5 (Compiler::Ai::opcodeToggleAI, new OpToggleAI); + interpreter.installSegment5 (Compiler::Ai::opcodeToggleAIExplicit, new OpToggleAI); interpreter.installSegment5 (Compiler::Ai::opcodeSetHello, new OpSetAiSetting(0)); interpreter.installSegment5 (Compiler::Ai::opcodeSetHelloExplicit, new OpSetAiSetting(0)); diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index 685741db6f..9e024f8723 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -356,5 +356,7 @@ op 0x2000220: DisableLevitation op 0x2000221: EnableLevitation op 0x2000222: GetLineOfSight op 0x2000223: GetLineOfSightExplicit +op 0x2000224: ToggleAI +op 0x2000225: ToggleAIExplicit -opcodes 0x2000224-0x3ffffff unused +opcodes 0x2000226-0x3ffffff unused diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index 2799c90db7..01792def95 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -59,6 +59,7 @@ namespace Compiler extensions.registerInstruction ("modfight", "l", opcodeModFight, opcodeModFightExplicit); extensions.registerInstruction ("modflee", "l", opcodeModFlee, opcodeModFleeExplicit); extensions.registerInstruction ("modalarm", "l", opcodeModAlarm, opcodeModAlarmExplicit); + extensions.registerInstruction ("toggleai", "", opcodeToggleAI, opcodeToggleAI); extensions.registerFunction ("gethello", 'l', "", opcodeGetHello, opcodeGetHelloExplicit); extensions.registerFunction ("getfight", 'l', "", opcodeGetFight, opcodeGetFightExplicit); extensions.registerFunction ("getflee", 'l', "", opcodeGetFlee, opcodeGetFleeExplicit); diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp index 582edcd33b..a885a373de 100644 --- a/components/compiler/opcodes.hpp +++ b/components/compiler/opcodes.hpp @@ -51,6 +51,8 @@ namespace Compiler const int opcodeGetAlarmExplicit = 0x20001c6; const int opcodeGetLineOfSight = 0x2000222; const int opcodeGetLineOfSightExplicit = 0x2000223; + const int opcodeToggleAI = 0x2000224; + const int opcodeToggleAIExplicit = 0x2000225; } namespace Animation From 16e5477c60b9e724fc758432eea915136caf511a Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 19 Nov 2013 02:17:44 +0100 Subject: [PATCH 309/434] Fix an uninitalized member --- apps/openmw/mwgui/mapwindow.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index 5ed002d7b3..93e1d11a3d 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -261,6 +261,7 @@ namespace MWGui : MWGui::WindowPinnableBase("openmw_map_window.layout") , mGlobal(false) , mGlobalMap(0) + , mGlobalMapRender(0) { setCoord(500,0,320,300); From cab535dd6918c08d60ab3350e769865d88a25585 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 19 Nov 2013 06:48:47 +0100 Subject: [PATCH 310/434] Implement magic item recharging via soulgem use --- apps/openmw/CMakeLists.txt | 1 + apps/openmw/mwbase/windowmanager.hpp | 1 + apps/openmw/mwgui/mode.hpp | 1 + apps/openmw/mwgui/recharge.cpp | 194 ++++++++++++++++++++++ apps/openmw/mwgui/recharge.hpp | 42 +++++ apps/openmw/mwgui/soulgemdialog.cpp | 3 +- apps/openmw/mwgui/windowmanagerimp.cpp | 13 ++ apps/openmw/mwgui/windowmanagerimp.hpp | 3 + files/mygui/CMakeLists.txt | 1 + files/mygui/openmw_recharge_dialog.layout | 29 ++++ 10 files changed, 287 insertions(+), 1 deletion(-) create mode 100644 apps/openmw/mwgui/recharge.cpp create mode 100644 apps/openmw/mwgui/recharge.hpp create mode 100644 files/mygui/openmw_recharge_dialog.layout diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index a36c179968..576b28ea1a 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -36,6 +36,7 @@ add_openmw_dir (mwgui merchantrepair repair soulgemdialog companionwindow bookpage journalviewmodel journalbooks keywordsearch itemmodel containeritemmodel inventoryitemmodel sortfilteritemmodel itemview tradeitemmodel companionitemmodel pickpocketitemmodel fontloader controllers savegamedialog + recharge ) add_openmw_dir (mwdialogue diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 1cd8672230..c6864de369 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -265,6 +265,7 @@ namespace MWBase virtual void showCompanionWindow(MWWorld::Ptr actor) = 0; virtual void startSpellMaking(MWWorld::Ptr actor) = 0; virtual void startEnchanting(MWWorld::Ptr actor) = 0; + virtual void startRecharge(MWWorld::Ptr soulgem) = 0; virtual void startSelfEnchanting(MWWorld::Ptr soulgem) = 0; virtual void startTraining(MWWorld::Ptr actor) = 0; virtual void startRepair(MWWorld::Ptr actor) = 0; diff --git a/apps/openmw/mwgui/mode.hpp b/apps/openmw/mwgui/mode.hpp index 879fcb483f..50d53abacd 100644 --- a/apps/openmw/mwgui/mode.hpp +++ b/apps/openmw/mwgui/mode.hpp @@ -28,6 +28,7 @@ namespace MWGui GM_Travel, GM_SpellCreation, GM_Enchanting, + GM_Recharge, GM_Training, GM_MerchantRepair, diff --git a/apps/openmw/mwgui/recharge.cpp b/apps/openmw/mwgui/recharge.cpp new file mode 100644 index 0000000000..7af9fbcfb9 --- /dev/null +++ b/apps/openmw/mwgui/recharge.cpp @@ -0,0 +1,194 @@ +#include "recharge.hpp" + +#include +#include + +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/windowmanager.hpp" + +#include "../mwworld/player.hpp" +#include "../mwworld/containerstore.hpp" +#include "../mwworld/class.hpp" + +#include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/npcstats.hpp" + +#include "widgets.hpp" + +namespace MWGui +{ + +Recharge::Recharge() + : WindowBase("openmw_recharge_dialog.layout") +{ + getWidget(mBox, "Box"); + getWidget(mView, "View"); + getWidget(mGemBox, "GemBox"); + getWidget(mGemIcon, "GemIcon"); + getWidget(mChargeLabel, "ChargeLabel"); + getWidget(mCancelButton, "CancelButton"); + + mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &Recharge::onCancel); + + setVisible(false); +} + +void Recharge::open() +{ + center(); +} + +void Recharge::start (const MWWorld::Ptr &item) +{ + std::string path = std::string("icons\\"); + path += MWWorld::Class::get(item).getInventoryIcon(item); + int pos = path.rfind("."); + path.erase(pos); + path.append(".dds"); + mGemIcon->setImageTexture (path); + mGemIcon->setUserString("ToolTipType", "ItemPtr"); + mGemIcon->setUserData(item); + + updateView(); +} + +void Recharge::updateView() +{ + MWWorld::Ptr gem = *mGemIcon->getUserData(); + + std::string soul = gem.getCellRef().mSoul; + const ESM::Creature *creature = MWBase::Environment::get().getWorld()->getStore().get().find(soul); + + mChargeLabel->setCaptionWithReplacing("#{sCharges} " + boost::lexical_cast(creature->mData.mSoul)); + + bool toolBoxVisible = (gem.getRefData().getCount() != 0); + mGemBox->setVisible(toolBoxVisible); + + bool toolBoxWasVisible = (mBox->getPosition().top != mGemBox->getPosition().top); + + if (toolBoxVisible && !toolBoxWasVisible) + { + // shrink + mBox->setPosition(mBox->getPosition() + MyGUI::IntPoint(0, mGemBox->getSize().height)); + mBox->setSize(mBox->getSize() - MyGUI::IntSize(0,mGemBox->getSize().height)); + } + else if (!toolBoxVisible && toolBoxWasVisible) + { + // expand + mBox->setPosition(MyGUI::IntPoint (mBox->getPosition().left, mGemBox->getPosition().top)); + mBox->setSize(mBox->getSize() + MyGUI::IntSize(0,mGemBox->getSize().height)); + } + + while (mView->getChildCount()) + MyGUI::Gui::getInstance().destroyWidget(mView->getChildAt(0)); + + int currentY = 0; + + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::ContainerStore& store = MWWorld::Class::get(player).getContainerStore(player); + for (MWWorld::ContainerStoreIterator iter (store.begin()); + iter!=store.end(); ++iter) + { + std::string enchantmentName = iter->getClass().getEnchantment(*iter); + if (enchantmentName.empty()) + continue; + const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find(enchantmentName); + if (iter->getCellRef().mEnchantmentCharge >= enchantment->mData.mCharge + || iter->getCellRef().mEnchantmentCharge == -1) + continue; + + MyGUI::TextBox* text = mView->createWidget ( + "SandText", MyGUI::IntCoord(8, currentY, mView->getWidth()-8, 18), MyGUI::Align::Default); + text->setCaption(MWWorld::Class::get(*iter).getName(*iter)); + text->setNeedMouseFocus(false); + currentY += 19; + + MyGUI::ImageBox* icon = mView->createWidget ( + "ImageBox", MyGUI::IntCoord(16, currentY, 32, 32), MyGUI::Align::Default); + std::string path = std::string("icons\\"); + path += MWWorld::Class::get(*iter).getInventoryIcon(*iter); + int pos = path.rfind("."); + path.erase(pos); + path.append(".dds"); + icon->setImageTexture (path); + icon->setUserString("ToolTipType", "ItemPtr"); + icon->setUserData(*iter); + icon->eventMouseButtonClick += MyGUI::newDelegate(this, &Recharge::onItemClicked); + icon->eventMouseWheel += MyGUI::newDelegate(this, &Recharge::onMouseWheel); + + Widgets::MWDynamicStatPtr chargeWidget = mView->createWidget + ("MW_ChargeBar", MyGUI::IntCoord(72, currentY+2, 199, 20), MyGUI::Align::Default); + chargeWidget->setValue(iter->getCellRef().mEnchantmentCharge, enchantment->mData.mCharge); + chargeWidget->setNeedMouseFocus(false); + + currentY += 32 + 4; + } + mView->setCanvasSize (MyGUI::IntSize(mView->getWidth(), std::max(mView->getHeight(), currentY))); +} + +void Recharge::onCancel(MyGUI::Widget *sender) +{ + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Recharge); +} + +void Recharge::onItemClicked(MyGUI::Widget *sender) +{ + MWWorld::Ptr gem = *mGemIcon->getUserData(); + + if (!gem.getRefData().getCount()) + return; + + MWWorld::Ptr item = *sender->getUserData(); + + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); + MWMechanics::NpcStats& npcStats = player.getClass().getNpcStats(player); + + float luckTerm = 0.1 * stats.getAttribute(ESM::Attribute::Luck).getModified(); + if (luckTerm < 1|| luckTerm > 10) + luckTerm = 1; + + float intelligenceTerm = 0.2 * stats.getAttribute(ESM::Attribute::Intelligence).getModified(); + + if (intelligenceTerm > 20) + intelligenceTerm = 20; + if (intelligenceTerm < 1) + intelligenceTerm = 1; + + float x = (npcStats.getSkill(ESM::Skill::Enchant).getModified() + intelligenceTerm + luckTerm) * stats.getFatigueTerm(); + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + if (roll < x) + { + std::string soul = gem.getCellRef().mSoul; + const ESM::Creature *creature = MWBase::Environment::get().getWorld()->getStore().get().find(soul); + + float restored = creature->mData.mSoul * (roll / x); + + const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find( + item.getClass().getEnchantment(item)); + item.getCellRef().mEnchantmentCharge = + std::min(item.getCellRef().mEnchantmentCharge + restored, static_cast(enchantment->mData.mCharge)); + } + + gem.getContainerStore()->remove(gem, 1, player); + + if (gem.getRefData().getCount() == 0) + { + std::string message = MWBase::Environment::get().getWorld()->getStore().get().find("sNotifyMessage51")->getString(); + message = boost::str(boost::format(message) % gem.getClass().getName(gem)); + MWBase::Environment::get().getWindowManager()->messageBox(message); + } + + updateView(); +} + +void Recharge::onMouseWheel(MyGUI::Widget* _sender, int _rel) +{ + if (mView->getViewOffset().top + _rel*0.3 > 0) + mView->setViewOffset(MyGUI::IntPoint(0, 0)); + else + mView->setViewOffset(MyGUI::IntPoint(0, mView->getViewOffset().top + _rel*0.3)); +} + +} diff --git a/apps/openmw/mwgui/recharge.hpp b/apps/openmw/mwgui/recharge.hpp new file mode 100644 index 0000000000..2ffc5e10f8 --- /dev/null +++ b/apps/openmw/mwgui/recharge.hpp @@ -0,0 +1,42 @@ +#ifndef OPENMW_MWGUI_RECHARGE_H +#define OPENMW_MWGUI_RECHARGE_H + +#include "windowbase.hpp" + +#include "../mwworld/ptr.hpp" + +namespace MWGui +{ + +class Recharge : public WindowBase +{ +public: + Recharge(); + + virtual void open(); + + void start (const MWWorld::Ptr& gem); + +protected: + MyGUI::Widget* mBox; + MyGUI::ScrollView* mView; + + MyGUI::Widget* mGemBox; + + MyGUI::ImageBox* mGemIcon; + + MyGUI::TextBox* mChargeLabel; + + MyGUI::Button* mCancelButton; + + void updateView(); + + void onItemClicked (MyGUI::Widget* sender); + void onCancel (MyGUI::Widget* sender); + void onMouseWheel(MyGUI::Widget* _sender, int _rel); + +}; + +} + +#endif diff --git a/apps/openmw/mwgui/soulgemdialog.cpp b/apps/openmw/mwgui/soulgemdialog.cpp index b95eec0b67..6d70c85d9d 100644 --- a/apps/openmw/mwgui/soulgemdialog.cpp +++ b/apps/openmw/mwgui/soulgemdialog.cpp @@ -21,7 +21,8 @@ namespace MWGui { if (button == 0) { - /// \todo show recharge enchanted item dialog here + MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Recharge); + MWBase::Environment::get().getWindowManager()->startRecharge(mSoulgem); } else { diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 83325de23a..b77d1a885b 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -43,6 +43,7 @@ #include "waitdialog.hpp" #include "enchantingdialog.hpp" #include "trainingwindow.hpp" +#include "recharge.hpp" #include "exposedwindow.hpp" #include "cursor.hpp" #include "merchantrepair.hpp" @@ -95,6 +96,7 @@ namespace MWGui , mTrainingWindow(NULL) , mMerchantRepair(NULL) , mSoulgemDialog(NULL) + , mRecharge(NULL) , mRepair(NULL) , mCompanionWindow(NULL) , mTranslationDataStorage (translationDataStorage) @@ -197,6 +199,7 @@ namespace MWGui mDragAndDrop->mDraggedWidget = 0; mDragAndDrop->mDragAndDropWidget = dragAndDropWidget; + mRecharge = new Recharge(); mMenu = new MainMenu(w,h); mMap = new MapWindow(""); mStatsWindow = new StatsWindow(); @@ -312,6 +315,7 @@ namespace MWGui delete mSoulgemDialog; delete mSoftwareCursor; delete mCursorManager; + delete mRecharge; cleanupGarbage(); @@ -375,6 +379,7 @@ namespace MWGui mRepair->setVisible(false); mCompanionWindow->setVisible(false); mInventoryWindow->setTrading(false); + mRecharge->setVisible(false); mHud->setVisible(mHudEnabled); @@ -492,6 +497,9 @@ namespace MWGui case GM_SpellCreation: mSpellCreationDialog->setVisible(true); break; + case GM_Recharge: + mRecharge->setVisible(true); + break; case GM_Enchanting: mEnchantingDialog->setVisible(true); break; @@ -1363,4 +1371,9 @@ namespace MWGui return mLoadingScreen; } + void WindowManager::startRecharge(MWWorld::Ptr soulgem) + { + mRecharge->start(soulgem); + } + } diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index badb333a7f..965c7d6382 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -77,6 +77,7 @@ namespace MWGui class MerchantRepair; class Repair; class SoulgemDialog; + class Recharge; class CompanionWindow; class WindowManager : public MWBase::WindowManager @@ -263,6 +264,7 @@ namespace MWGui virtual void startTraining(MWWorld::Ptr actor); virtual void startRepair(MWWorld::Ptr actor); virtual void startRepairItem(MWWorld::Ptr item); + virtual void startRecharge(MWWorld::Ptr soulgem); virtual void frameStarted(float dt); @@ -313,6 +315,7 @@ namespace MWGui MerchantRepair* mMerchantRepair; SoulgemDialog* mSoulgemDialog; Repair* mRepair; + Recharge* mRecharge; CompanionWindow* mCompanionWindow; Translation::Storage& mTranslationDataStorage; diff --git a/files/mygui/CMakeLists.txt b/files/mygui/CMakeLists.txt index f79f2c3ed6..70fdf42489 100644 --- a/files/mygui/CMakeLists.txt +++ b/files/mygui/CMakeLists.txt @@ -81,6 +81,7 @@ set(MYGUI_FILES openmw_repair.layout openmw_companion_window.layout openmw_savegame_dialog.layout + openmw_recharge_dialog.layout smallbars.png DejaVuLGCSansMono.ttf markers.png diff --git a/files/mygui/openmw_recharge_dialog.layout b/files/mygui/openmw_recharge_dialog.layout new file mode 100644 index 0000000000..49e7357645 --- /dev/null +++ b/files/mygui/openmw_recharge_dialog.layout @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 74e42a2d02d1a1e37df1a2aeccacae7c77ac70de Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 19 Nov 2013 06:55:53 +0100 Subject: [PATCH 311/434] Add missing skill increases for Enchant skill --- apps/openmw/mwclass/npc.cpp | 3 +++ apps/openmw/mwgui/recharge.cpp | 2 ++ apps/openmw/mwmechanics/enchanting.cpp | 2 +- apps/openmw/mwmechanics/spellcasting.cpp | 3 +++ 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 5c67f17afb..e9182d0941 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -471,6 +471,9 @@ namespace MWClass MWMechanics::CastSpell cast(ptr, victim); cast.cast(weapon); + + if (ptr.getRefData().getHandle() == "player") + skillUsageSucceeded (ptr, ESM::Skill::Enchant, 3); } } } diff --git a/apps/openmw/mwgui/recharge.cpp b/apps/openmw/mwgui/recharge.cpp index 7af9fbcfb9..b700360bac 100644 --- a/apps/openmw/mwgui/recharge.cpp +++ b/apps/openmw/mwgui/recharge.cpp @@ -169,6 +169,8 @@ void Recharge::onItemClicked(MyGUI::Widget *sender) item.getClass().getEnchantment(item)); item.getCellRef().mEnchantmentCharge = std::min(item.getCellRef().mEnchantmentCharge + restored, static_cast(enchantment->mData.mCharge)); + + player.getClass().skillUsageSucceeded (player, ESM::Skill::Enchant, 0); } gem.getContainerStore()->remove(gem, 1, player); diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index 5d148d1106..ba53a1a725 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -72,7 +72,7 @@ namespace MWMechanics if(getEnchantChance() (RAND_MAX)*100) return false; - MWWorld::Class::get (mEnchanter).skillUsageSucceeded (mEnchanter, ESM::Skill::Enchant, 1); + MWWorld::Class::get (mEnchanter).skillUsageSucceeded (mEnchanter, ESM::Skill::Enchant, 2); } if(mCastStyle==ESM::Enchantment::ConstantEffect) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 5763549cf5..f733472063 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -303,6 +303,9 @@ namespace MWMechanics MWBase::Environment::get().getWindowManager()->setSelectedEnchantItem(item); // Set again to show the modified charge } + if (mCaster.getRefData().getHandle() == "player") + mCaster.getClass().skillUsageSucceeded (mCaster, ESM::Skill::Enchant, 1); + inflict(mCaster, mCaster, enchantment->mEffects, ESM::RT_Self); if (!mTarget.isEmpty()) From e8dcd747416eacd5e098609b2bf6f9c598ec9584 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 19 Nov 2013 16:42:24 +0100 Subject: [PATCH 312/434] Recharge enchanted items in player's inventory over time --- apps/openmw/mwbase/mechanicsmanager.hpp | 2 ++ .../mwmechanics/mechanicsmanagerimp.cpp | 8 +++++ .../mwmechanics/mechanicsmanagerimp.hpp | 2 ++ apps/openmw/mwworld/inventorystore.cpp | 36 +++++++++++++++++++ apps/openmw/mwworld/inventorystore.hpp | 8 +++++ apps/openmw/mwworld/worldimp.cpp | 2 ++ 6 files changed, 58 insertions(+) diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 24dc569d8c..c8db203252 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -59,6 +59,8 @@ namespace MWBase /// \param paused In game type does not currently advance (this usually means some GUI /// component is up). + virtual void advanceTime (float duration) = 0; + virtual void setPlayerName (const std::string& name) = 0; ///< Set player name. diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index e1a7ac1231..8ae413b0a5 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -213,6 +213,14 @@ namespace MWMechanics mWatched = ptr; } + void MechanicsManager::advanceTime (float duration) + { + // Uses ingame time, but scaled to real time + duration /= MWBase::Environment::get().getWorld()->getTimeScaleFactor(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + player.getClass().getInventoryStore(player).rechargeItems(duration); + } + void MechanicsManager::update(float duration, bool paused) { if(!mWatched.isEmpty()) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 42656d5abc..57309a6f09 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -63,6 +63,8 @@ namespace MWMechanics /// \param paused In game type does not currently advance (this usually means some GUI /// component is up). + virtual void advanceTime (float duration); + virtual void setPlayerName (const std::string& name); ///< Set player name. diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 6483b32b2e..349c664c3f 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -88,6 +88,8 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::add(const Ptr& itemPtr, autoEquip(actorPtr); } + updateRechargingItems(); + return retVal; } @@ -459,6 +461,8 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor MWBase::Environment::get().getWindowManager()->unsetSelectedSpell(); } + updateRechargingItems(); + return retCount; } @@ -577,3 +581,35 @@ void MWWorld::InventoryStore::visitEffectSources(MWMechanics::EffectSourceVisito } } } + +void MWWorld::InventoryStore::updateRechargingItems() +{ + mRechargingItems.clear(); + for (ContainerStoreIterator it = begin(); it != end(); ++it) + { + if (it->getClass().getEnchantment(*it) != "") + { + const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find( + it->getClass().getEnchantment(*it)); + if (enchantment->mData.mType == ESM::Enchantment::WhenUsed + || enchantment->mData.mType == ESM::Enchantment::WhenStrikes) + mRechargingItems.push_back(std::make_pair(it, enchantment->mData.mCharge)); + } + } +} + +void MWWorld::InventoryStore::rechargeItems(float duration) +{ + for (TRechargingItems::iterator it = mRechargingItems.begin(); it != mRechargingItems.end(); ++it) + { + if (it->first->getCellRef().mEnchantmentCharge == -1 + || it->first->getCellRef().mEnchantmentCharge == it->second) + continue; + + static float fMagicItemRechargePerSecond = MWBase::Environment::get().getWorld()->getStore().get().find( + "fMagicItemRechargePerSecond")->getFloat(); + + it->first->getCellRef().mEnchantmentCharge = std::min (it->first->getCellRef().mEnchantmentCharge + fMagicItemRechargePerSecond * duration, + it->second); + } +} diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index f53ce8efb9..58ff50ead0 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -92,11 +92,16 @@ namespace MWWorld // selected magic item (for using enchantments of type "Cast once" or "Cast when used") ContainerStoreIterator mSelectedEnchantItem; + // (item, max charge) + typedef std::vector > TRechargingItems; + TRechargingItems mRechargingItems; + void copySlots (const InventoryStore& store); void initSlots (TSlots& slots_); void updateMagicEffects(const Ptr& actor); + void updateRechargingItems(); void fireEquipmentChangedEvent(); @@ -172,6 +177,9 @@ namespace MWWorld ///< Set a listener for various events, see \a InventoryStoreListener void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor); + + void rechargeItems (float duration); + /// Restore charge on enchanted items. Note this should only be done for the player. }; } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 7dec848ad3..7702ea2502 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -607,6 +607,8 @@ namespace MWWorld void World::advanceTime (double hours) { + MWBase::Environment::get().getMechanicsManager()->advanceTime(hours*3600); + mWeatherManager->advanceTime (hours); hours += mGlobalVariables->getFloat ("gamehour"); From 654b7d9ba5e61bd5522383d75d8f373bb42a89a7 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 19 Nov 2013 16:52:26 +0100 Subject: [PATCH 313/434] Apply disease resistance manually as according to wiki --- apps/openmw/mwmechanics/spellcasting.cpp | 35 ++++++++++++++++++------ 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index f733472063..ac5be48918 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -95,18 +95,37 @@ namespace MWMechanics } // Try resisting - if (magnitudeMult > 0 && caster.getClass().isActor()) + if (magnitudeMult > 0 && target.getClass().isActor()) { const ESM::Spell *spell = MWBase::Environment::get().getWorld()->getStore().get().search (mId); - magnitudeMult = MWMechanics::getEffectMultiplier(effectIt->mEffectID, target, caster, spell); - if (magnitudeMult == 0) + + if (spell->mData.mType == ESM::Spell::ST_Disease || spell->mData.mType == ESM::Spell::ST_Blight) { - // Fully resisted, show message - if (target.getRefData().getHandle() == "player") - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); - else - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}"); + float x = (spell->mData.mType == ESM::Spell::ST_Disease) ? + target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::ResistCommonDisease).mMagnitude + : target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::ResistBlightDisease).mMagnitude; + + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + if (roll <= x) + { + // Fully resisted, show message + if (target.getRefData().getHandle() == "player") + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); + magnitudeMult = 0; + } + } + else + { + magnitudeMult = MWMechanics::getEffectMultiplier(effectIt->mEffectID, target, caster, spell); + if (magnitudeMult == 0) + { + // Fully resisted, show message + if (target.getRefData().getHandle() == "player") + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); + else + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}"); + } } } } From c03c82c78a18aa4d38da4efc3526849e95f4883b Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 19 Nov 2013 16:52:26 +0100 Subject: [PATCH 314/434] Apply disease resistance manually as according to wiki --- apps/openmw/mwmechanics/spellcasting.cpp | 52 +++++++++++------------- apps/openmw/mwmechanics/spellcasting.hpp | 2 +- 2 files changed, 25 insertions(+), 29 deletions(-) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index ac5be48918..af149b3bab 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -35,6 +35,23 @@ namespace MWMechanics if (!found) return; + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search (mId); + if (spell && (spell->mData.mType == ESM::Spell::ST_Disease || spell->mData.mType == ESM::Spell::ST_Blight)) + { + float x = (spell->mData.mType == ESM::Spell::ST_Disease) ? + target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::ResistCommonDisease).mMagnitude + : target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::ResistBlightDisease).mMagnitude; + + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + if (roll <= x) + { + // Fully resisted, show message + if (target.getRefData().getHandle() == "player") + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); + return; + } + } + ESM::EffectList reflectedEffects; std::vector appliedLastingEffects; bool firstAppliedEffect = true; @@ -59,7 +76,6 @@ namespace MWMechanics // Try absorbing if it's a spell // NOTE: Vanilla does this once per effect source instead of adding the % from all sources together, not sure // if that is worth replicating. - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search (mId); if (spell && caster != target) { int absorb = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).mMagnitude; @@ -97,35 +113,15 @@ namespace MWMechanics // Try resisting if (magnitudeMult > 0 && target.getClass().isActor()) { - const ESM::Spell *spell = - MWBase::Environment::get().getWorld()->getStore().get().search (mId); - if (spell->mData.mType == ESM::Spell::ST_Disease || spell->mData.mType == ESM::Spell::ST_Blight) + magnitudeMult = MWMechanics::getEffectMultiplier(effectIt->mEffectID, target, caster, spell); + if (magnitudeMult == 0) { - float x = (spell->mData.mType == ESM::Spell::ST_Disease) ? - target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::ResistCommonDisease).mMagnitude - : target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::ResistBlightDisease).mMagnitude; - - int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] - if (roll <= x) - { - // Fully resisted, show message - if (target.getRefData().getHandle() == "player") - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); - magnitudeMult = 0; - } - } - else - { - magnitudeMult = MWMechanics::getEffectMultiplier(effectIt->mEffectID, target, caster, spell); - if (magnitudeMult == 0) - { - // Fully resisted, show message - if (target.getRefData().getHandle() == "player") - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); - else - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}"); - } + // Fully resisted, show message + if (target.getRefData().getHandle() == "player") + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); + else + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}"); } } } diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index 5e87e19933..c40567fcfa 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -140,7 +140,7 @@ namespace MWMechanics float x = (willpower + 0.1 * luck) * stats.getFatigueTerm(); // This makes spells that are easy to cast harder to resist and vice versa - if (spell != NULL) + if (spell != NULL && caster.getClass().isActor()) { float castChance = getSpellSuccessChance(spell, caster); if (castChance > 0) From f3ff2e4260d39c1b0e7a678b586e5a7f87ae3d48 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 19 Nov 2013 17:33:02 +0100 Subject: [PATCH 315/434] Handle Unreflectable flag --- apps/openmw/mwmechanics/spellcasting.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index af149b3bab..859d8d6991 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -95,7 +95,7 @@ namespace MWMechanics } // Try reflecting - if (!reflected && magnitudeMult > 0 && caster != target) + if (!reflected && magnitudeMult > 0 && caster != target && !(magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable)) { int reflect = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Reflect).mMagnitude; int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] From 8b095982e930912e04e46d93803d8430d252fd2a Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 19 Nov 2013 18:42:43 +0100 Subject: [PATCH 316/434] Don't auto equip for dead actors --- apps/openmw/mwworld/inventorystore.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 349c664c3f..69e06378a6 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -81,7 +81,8 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::add(const Ptr& itemPtr, // Auto-equip items if an armor/clothing item is added, but not for the player nor werewolves if ((actorPtr.getRefData().getHandle() != "player") - && !(MWWorld::Class::get(actorPtr).getNpcStats(actorPtr).isWerewolf())) + && !(MWWorld::Class::get(actorPtr).getNpcStats(actorPtr).isWerewolf()) + && !actorPtr.getClass().getCreatureStats(actorPtr).isDead()) { std::string type = itemPtr.getTypeName(); if ((type == typeid(ESM::Armor).name()) || (type == typeid(ESM::Clothing).name())) @@ -450,7 +451,8 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor && !(MWWorld::Class::get(actor).getNpcStats(actor).isWerewolf())) { std::string type = item.getTypeName(); - if ((type == typeid(ESM::Armor).name()) || (type == typeid(ESM::Clothing).name())) + if (((type == typeid(ESM::Armor).name()) || (type == typeid(ESM::Clothing).name())) + && !actor.getClass().getCreatureStats(actor).isDead()) autoEquip(actor); } From 38a82c4b0bce8530a8e692bb047b3c06c995cf99 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 19 Nov 2013 18:43:21 +0100 Subject: [PATCH 317/434] Add a todo comment --- apps/openmw/mwmechanics/activespells.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index 001402337c..b3f499c6b2 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -36,6 +36,9 @@ namespace MWMechanics std::vector mEffects; MWWorld::TimeStamp mTimeStamp; std::string mDisplayName; + + // TODO: To handle CASTER_LINKED flag (spell is purged when caster dies), + // we should probably store a handle to the caster here. }; typedef std::multimap TContainer; From 0b9676aaa3d0085ae12c4a30b8348013b0e225ae Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 19 Nov 2013 18:43:32 +0100 Subject: [PATCH 318/434] Fix an issue with the AI code --- apps/openmw/mwmechanics/actors.cpp | 67 ++++++++++++++---------------- 1 file changed, 32 insertions(+), 35 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 5623dd36fb..ec22c8d7f5 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -39,46 +39,42 @@ namespace MWMechanics if(!MWBase::Environment::get().getWindowManager()->isGuiMode()) { // AI - if(MWBase::Environment::get().getMechanicsManager())//check MechanismsManager is already created + if(MWBase::Environment::get().getMechanicsManager()->isAIActive()) { - if(MWBase::Environment::get().getMechanicsManager()->isAIActive())//MWBase::Environment::get().getMechanicsManager()->isAIActive()) + CreatureStats& creatureStats = MWWorld::Class::get (ptr).getCreatureStats (ptr); + //engage combat or not? + if(ptr != MWBase::Environment::get().getWorld()->getPlayer().getPlayer() && !creatureStats.isHostile()) { - CreatureStats& creatureStats = MWWorld::Class::get (ptr).getCreatureStats (ptr); - if(ptr != MWBase::Environment::get().getWorld()->getPlayer().getPlayer()) MWBase::Environment::get().getMechanicsManager()->restoreDynamicStats(); - //engage combat or not? - if(ptr != MWBase::Environment::get().getWorld()->getPlayer().getPlayer() && !creatureStats.isHostile()) + ESM::Position playerpos = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getRefData().getPosition(); + ESM::Position actorpos = ptr.getRefData().getPosition(); + float d = sqrt((actorpos.pos[0] - playerpos.pos[0])*(actorpos.pos[0] - playerpos.pos[0]) + +(actorpos.pos[1] - playerpos.pos[1])*(actorpos.pos[1] - playerpos.pos[1]) + +(actorpos.pos[2] - playerpos.pos[2])*(actorpos.pos[2] - playerpos.pos[2])); + float fight = ptr.getClass().getCreatureStats(ptr).getAiSetting(1); + float disp = 100; //creatures don't have disposition, so set it to 100 by default + if(ptr.getTypeName() == typeid(ESM::NPC).name()) { - ESM::Position playerpos = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getRefData().getPosition(); - ESM::Position actorpos = ptr.getRefData().getPosition(); - float d = sqrt((actorpos.pos[0] - playerpos.pos[0])*(actorpos.pos[0] - playerpos.pos[0]) - +(actorpos.pos[1] - playerpos.pos[1])*(actorpos.pos[1] - playerpos.pos[1]) - +(actorpos.pos[2] - playerpos.pos[2])*(actorpos.pos[2] - playerpos.pos[2])); - float fight = ptr.getClass().getCreatureStats(ptr).getAiSetting(1); - float disp = 100; //creatures don't have disposition, so set it to 100 by default - if(ptr.getTypeName() == typeid(ESM::NPC).name()) - { - disp = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(ptr); - } - bool LOS = MWBase::Environment::get().getWorld()->getLOS(ptr,MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); - if( ( (fight == 100 ) - || (fight >= 95 && d <= 3000) - || (fight >= 90 && d <= 2000) - || (fight >= 80 && d <= 1000) - || (fight >= 80 && disp <= 40) - || (fight >= 70 && disp <= 35 && d <= 1000) - || (fight >= 60 && disp <= 30 && d <= 1000) - || (fight >= 50 && disp == 0) - || (fight >= 40 && disp <= 10 && d <= 500) ) - && LOS - ) - { - creatureStats.getAiSequence().stack(AiCombat("player")); - creatureStats.setHostile(true); - } + disp = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(ptr); + } + bool LOS = MWBase::Environment::get().getWorld()->getLOS(ptr,MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); + if( ( (fight == 100 ) + || (fight >= 95 && d <= 3000) + || (fight >= 90 && d <= 2000) + || (fight >= 80 && d <= 1000) + || (fight >= 80 && disp <= 40) + || (fight >= 70 && disp <= 35 && d <= 1000) + || (fight >= 60 && disp <= 30 && d <= 1000) + || (fight >= 50 && disp == 0) + || (fight >= 40 && disp <= 10 && d <= 500) ) + && LOS + ) + { + creatureStats.getAiSequence().stack(AiCombat("player")); + creatureStats.setHostile(true); } - - creatureStats.getAiSequence().execute (ptr,duration); } + + creatureStats.getAiSequence().execute (ptr,duration); } // fatigue restoration @@ -212,6 +208,7 @@ namespace MWMechanics stat.setModifier(effects.get(EffectKey(ESM::MagicEffect::FortifyHealth+i)).mMagnitude - effects.get(EffectKey(ESM::MagicEffect::DrainHealth+i)).mMagnitude); + float currentDiff = creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::RestoreHealth+i)).mMagnitude - creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::DamageHealth+i)).mMagnitude - creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::AbsorbHealth)).mMagnitude; From 9b34e4a523a0251631a4e55b94b2e0ffe8085439 Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Tue, 19 Nov 2013 20:35:57 +0100 Subject: [PATCH 319/434] Creating new files. --- manual/opencs/creating_file.tex | 0 manual/opencs/files_and_directories.tex | 24 ++++++++++++++++++++++++ manual/opencs/main.tex | 2 ++ 3 files changed, 26 insertions(+) create mode 100644 manual/opencs/creating_file.tex create mode 100644 manual/opencs/files_and_directories.tex diff --git a/manual/opencs/creating_file.tex b/manual/opencs/creating_file.tex new file mode 100644 index 0000000000..e69de29bb2 diff --git a/manual/opencs/files_and_directories.tex b/manual/opencs/files_and_directories.tex new file mode 100644 index 0000000000..4938ff0a9a --- /dev/null +++ b/manual/opencs/files_and_directories.tex @@ -0,0 +1,24 @@ +\section{Files and Directories} +\subsection{Introduction} +As you imagine, there is no other way to store and distribute computer data without files. You surely know that each file in your file system is identified by the unique path. This is basic knowledge, and applies to both Open{MW} and Open{CS} alike.\\ + +Needless to say, this chapter describes paths and files that are used/created/edited with OpenCS editor.\\ + +\subsection{Used terms} %TODO + +\subsection{Basics} + +\paragraph{Directories} +Open{CS} user should consider two directory paths in his system. So called ``User Configuration Path'' and ``Data Path''. ``User Configuration Path'' stores basically all Open{MW} specific files that are supposed to be touched by the user, while ``Data Path''%This is wrong, pay no mind to it. + +\paragraph{Files} +Bethesda Morrowind engine is using two types of files: esm (master) and esp (plugin). The distinction between those is not clear, and often confusing, mostly because large esp files could hurt stability. Open{MW} supports both esm and esp files, but in order to make use of new features of OpenMW one should consider using new file types designed with our engine in mind: game files and addon files.\\ + +Game and Addon files are concept descended from old esm/esp system, but much more flexible and cleaner. Finally, We can describe the difference between those two file types use case with pure statements. If you want to make new game using Open{MW} as engine (so called ``total conversion'') you should create a game file. If you want to create a addon for existing game file -- simply create addon file. The addon size is not a factor here. The only distinction you should consider is if your project is about changing other game, or creating a new one. Simple as that. + +%TODO describe some characteristics, like file extensions. What about project files? + +The actual creating of new files is described in the next chapter. Here We are gonna focus only on details that you need to know in order to create your first Open{CS} file while full understanding your needs. + +\paragraph{Dependencies} +Since addon is supposed to change the game it is logical that it also depends on the said game. It simply can't work otherwise. Just think about it: your modification is changing prize of the iron sword. But what if there is no iron sword in game? That's right: we get nonsense. What you want to do is to tie your addon to the files you are changing. Those can be either game files (expansion island for a game) or other addon files (house on the said island). It is a a good idea to be dependent only on files that are really changed in your addon obviously, but sadly there is no other way to achieve this than knowing what you want to do. \ No newline at end of file diff --git a/manual/opencs/main.tex b/manual/opencs/main.tex index 603e2d9ba3..577b7078b3 100644 --- a/manual/opencs/main.tex +++ b/manual/opencs/main.tex @@ -8,6 +8,8 @@ \title{OpenCS User Manual} \maketitle \tableofcontents{} +\input{files_and_directories} +\input{creating_file} \input{windows} \input{tables} \input{filters} From 9bf38372180032739fbd9ffb370276e1ac9aa2e5 Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Tue, 19 Nov 2013 20:38:22 +0100 Subject: [PATCH 320/434] minor edit --- manual/opencs/files_and_directories.tex | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/manual/opencs/files_and_directories.tex b/manual/opencs/files_and_directories.tex index 4938ff0a9a..530d7a76a3 100644 --- a/manual/opencs/files_and_directories.tex +++ b/manual/opencs/files_and_directories.tex @@ -11,7 +11,7 @@ Needless to say, this chapter describes paths and files that are used/created/ed \paragraph{Directories} Open{CS} user should consider two directory paths in his system. So called ``User Configuration Path'' and ``Data Path''. ``User Configuration Path'' stores basically all Open{MW} specific files that are supposed to be touched by the user, while ``Data Path''%This is wrong, pay no mind to it. -\paragraph{Files} +\paragraph{Content files} Bethesda Morrowind engine is using two types of files: esm (master) and esp (plugin). The distinction between those is not clear, and often confusing, mostly because large esp files could hurt stability. Open{MW} supports both esm and esp files, but in order to make use of new features of OpenMW one should consider using new file types designed with our engine in mind: game files and addon files.\\ Game and Addon files are concept descended from old esm/esp system, but much more flexible and cleaner. Finally, We can describe the difference between those two file types use case with pure statements. If you want to make new game using Open{MW} as engine (so called ``total conversion'') you should create a game file. If you want to create a addon for existing game file -- simply create addon file. The addon size is not a factor here. The only distinction you should consider is if your project is about changing other game, or creating a new one. Simple as that. @@ -21,4 +21,6 @@ Game and Addon files are concept descended from old esm/esp system, but much mor The actual creating of new files is described in the next chapter. Here We are gonna focus only on details that you need to know in order to create your first Open{CS} file while full understanding your needs. \paragraph{Dependencies} -Since addon is supposed to change the game it is logical that it also depends on the said game. It simply can't work otherwise. Just think about it: your modification is changing prize of the iron sword. But what if there is no iron sword in game? That's right: we get nonsense. What you want to do is to tie your addon to the files you are changing. Those can be either game files (expansion island for a game) or other addon files (house on the said island). It is a a good idea to be dependent only on files that are really changed in your addon obviously, but sadly there is no other way to achieve this than knowing what you want to do. \ No newline at end of file +Since addon is supposed to change the game it is logical that it also depends on the said game. It simply can't work otherwise. Just think about it: your modification is changing prize of the iron sword. But what if there is no iron sword in game? That's right: we get nonsense. What you want to do is to tie your addon to the files you are changing. Those can be either game files (expansion island for a game) or other addon files (house on the said island). It is a a good idea to be dependent only on files that are really changed in your addon obviously, but sadly there is no other way to achieve this than knowing what you want to do. + +\paragraph{Resources files} From 240a44f932292db6a5afcab9d12c47c2eeb3eff6 Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Tue, 19 Nov 2013 20:43:29 +0100 Subject: [PATCH 321/434] Files chapter will actually require resarch to be done :/ --- manual/opencs/files_and_directories.tex | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/manual/opencs/files_and_directories.tex b/manual/opencs/files_and_directories.tex index 530d7a76a3..e88018af2f 100644 --- a/manual/opencs/files_and_directories.tex +++ b/manual/opencs/files_and_directories.tex @@ -21,6 +21,11 @@ Game and Addon files are concept descended from old esm/esp system, but much mor The actual creating of new files is described in the next chapter. Here We are gonna focus only on details that you need to know in order to create your first Open{CS} file while full understanding your needs. \paragraph{Dependencies} -Since addon is supposed to change the game it is logical that it also depends on the said game. It simply can't work otherwise. Just think about it: your modification is changing prize of the iron sword. But what if there is no iron sword in game? That's right: we get nonsense. What you want to do is to tie your addon to the files you are changing. Those can be either game files (expansion island for a game) or other addon files (house on the said island). It is a a good idea to be dependent only on files that are really changed in your addon obviously, but sadly there is no other way to achieve this than knowing what you want to do. +Since addon is supposed to change the game it is logical that it also depends on the said game. It simply can't work otherwise. Just think about it: your modification is changing prize of the iron sword. But what if there is no iron sword in game? That's right: we get nonsense. What you want to do is to tie your addon to the files you are changing. Those can be either game files (expansion island for a game) or other addon files (house on the said island). It is a a good idea to be dependent only on files that are really changed in your addon obviously, but sadly there is no other way to achieve this than knowing what you want to do.\\ + +Game files are not intend to have any dependencies for a very simple reasons: player is using only one game file at the time and therefore no game file can depend on other game file, and since game file makes the base for addon files -- it can't depend on addon files. + +\paragraph{Loading order} \paragraph{Resources files} +%textures, sounds, whatever \ No newline at end of file From 3452bd2e0b59903673509dac6eb3bfc2b8c7cceb Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 20 Nov 2013 00:07:26 +0100 Subject: [PATCH 322/434] Add glow effect for enchanted items --- apps/openmw/mwrender/animation.cpp | 60 +++++++++++++++++- apps/openmw/mwrender/animation.hpp | 7 ++- apps/openmw/mwrender/npcanimation.cpp | 23 ++++--- apps/openmw/mwrender/npcanimation.hpp | 9 ++- apps/openmw/mwrender/objects.hpp | 2 + components/esm/loadmgef.hpp | 5 +- extern/shiny/Main/Factory.hpp | 3 +- .../Platforms/Ogre/OgreTextureUnitState.cpp | 29 +++++++++ files/materials/objects.mat | 12 ++++ files/materials/objects.shader | 63 ++++++++++++++----- 10 files changed, 180 insertions(+), 33 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index a29beb35c8..15df907b2f 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -17,6 +17,8 @@ #include "../mwbase/soundmanager.hpp" #include "../mwbase/world.hpp" +#include + #include "../mwmechanics/character.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwworld/class.hpp" @@ -24,6 +26,7 @@ #include "renderconst.hpp" + namespace MWRender { @@ -166,8 +169,37 @@ void Animation::setObjectRoot(const std::string &model, bool baseonly) } } +struct AddGlow +{ + Ogre::Vector3* mColor; + AddGlow(Ogre::Vector3* col) : mColor(col) {} -class VisQueueSet { + // TODO: integrate this with material controllers? + void operator()(Ogre::Entity* entity) const + { + unsigned int numsubs = entity->getNumSubEntities(); + for(unsigned int i = 0;i < numsubs;++i) + { + unsigned int numsubs = entity->getNumSubEntities(); + for(unsigned int i = 0;i < numsubs;++i) + { + Ogre::SubEntity* subEnt = entity->getSubEntity(i); + std::string newName = subEnt->getMaterialName() + "@fx"; + if (sh::Factory::getInstance().searchInstance(newName) == NULL) + { + sh::MaterialInstance* instance = + sh::Factory::getInstance().createMaterialInstance(newName, subEnt->getMaterialName()); + instance->setProperty("env_map", sh::makeProperty(new sh::BooleanValue(true))); + instance->setProperty("env_map_color", sh::makeProperty(new sh::Vector3(mColor->x, mColor->y, mColor->z))); + } + subEnt->setMaterialName(newName); + } + } + } +}; + +class VisQueueSet +{ Ogre::uint32 mVisFlags; Ogre::uint8 mSolidQueue, mTransQueue; Ogre::Real mDist; @@ -201,12 +233,16 @@ public: } }; -void Animation::setRenderProperties(const NifOgre::ObjectList &objlist, Ogre::uint32 visflags, Ogre::uint8 solidqueue, Ogre::uint8 transqueue, Ogre::Real dist) +void Animation::setRenderProperties(const NifOgre::ObjectList &objlist, Ogre::uint32 visflags, Ogre::uint8 solidqueue, Ogre::uint8 transqueue, Ogre::Real dist, bool enchantedGlow, Ogre::Vector3* glowColor) { std::for_each(objlist.mEntities.begin(), objlist.mEntities.end(), VisQueueSet(visflags, solidqueue, transqueue, dist)); std::for_each(objlist.mParticles.begin(), objlist.mParticles.end(), VisQueueSet(visflags, solidqueue, transqueue, dist)); + + if (enchantedGlow) + std::for_each(objlist.mEntities.begin(), objlist.mEntities.end(), + AddGlow(glowColor)); } @@ -1116,6 +1152,23 @@ void Animation::updateEffects(float duration) } } +// TODO: Should not be here +Ogre::Vector3 Animation::getEnchantmentColor(MWWorld::Ptr item) +{ + Ogre::Vector3 result(1,1,1); + std::string enchantmentName = item.getClass().getEnchantment(item); + if (enchantmentName.empty()) + return result; + const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find(enchantmentName); + assert (enchantment->mEffects.mList.size()); + const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find( + enchantment->mEffects.mList.front().mEffectID); + result.x = magicEffect->mData.mRed / 255.f; + result.y = magicEffect->mData.mGreen / 255.f; + result.z = magicEffect->mData.mBlue / 255.f; + return result; +} + ObjectAnimation::ObjectAnimation(const MWWorld::Ptr& ptr, const std::string &model) : Animation(ptr, ptr.getRefData().getBaseNode()) @@ -1132,9 +1185,10 @@ ObjectAnimation::ObjectAnimation(const MWWorld::Ptr& ptr, const std::string &mod small = false; float dist = small ? Settings::Manager::getInt("small object distance", "Viewing distance") : 0.0f; + Ogre::Vector3 col = getEnchantmentColor(ptr); setRenderProperties(mObjectRoot, (mPtr.getTypeName() == typeid(ESM::Static).name()) ? (small ? RV_StaticsSmall : RV_Statics) : RV_Misc, - RQG_Main, RQG_Alpha, dist); + RQG_Main, RQG_Alpha, dist, !ptr.getClass().getEnchantment(ptr).empty(), &col); } void ObjectAnimation::addLight(const ESM::Light *light) diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 7d1fd010ca..38e7742ef1 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -191,10 +191,15 @@ protected: static void destroyObjectList(Ogre::SceneManager *sceneMgr, NifOgre::ObjectList &objects); - static void setRenderProperties(const NifOgre::ObjectList &objlist, Ogre::uint32 visflags, Ogre::uint8 solidqueue, Ogre::uint8 transqueue, Ogre::Real dist=0.0f); + static void setRenderProperties(const NifOgre::ObjectList &objlist, Ogre::uint32 visflags, Ogre::uint8 solidqueue, + Ogre::uint8 transqueue, Ogre::Real dist=0.0f, + bool enchantedGlow=false, Ogre::Vector3* glowColor=NULL); void clearAnimSources(); + // TODO: Should not be here + Ogre::Vector3 getEnchantmentColor(MWWorld::Ptr item); + public: Animation(const MWWorld::Ptr &ptr, Ogre::SceneNode *node); virtual ~Animation(); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index bc7e48af1b..c714289416 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -208,17 +208,19 @@ void NpcAnimation::updateParts() removeIndividualPart(ESM::PRT_Hair); int prio = 1; + bool enchantedGlow = !store->getClass().getEnchantment(*store).empty(); + Ogre::Vector3 glowColor = getEnchantmentColor(*store); if(store->getTypeName() == typeid(ESM::Clothing).name()) { prio = ((slotlist[i].mBasePriority+1)<<1) + 0; const ESM::Clothing *clothes = store->get()->mBase; - addPartGroup(slotlist[i].mSlot, prio, clothes->mParts.mParts); + addPartGroup(slotlist[i].mSlot, prio, clothes->mParts.mParts, enchantedGlow, &glowColor); } else if(store->getTypeName() == typeid(ESM::Armor).name()) { prio = ((slotlist[i].mBasePriority+1)<<1) + 1; const ESM::Armor *armor = store->get()->mBase; - addPartGroup(slotlist[i].mSlot, prio, armor->mParts.mParts); + addPartGroup(slotlist[i].mSlot, prio, armor->mParts.mParts, enchantedGlow, &glowColor); } if(slotlist[i].mSlot == MWWorld::InventoryStore::Slot_Robe) @@ -389,10 +391,11 @@ public: } }; -NifOgre::ObjectList NpcAnimation::insertBoundedPart(const std::string &model, int group, const std::string &bonename) +NifOgre::ObjectList NpcAnimation::insertBoundedPart(const std::string &model, int group, const std::string &bonename, bool enchantedGlow, Ogre::Vector3* glowColor) { NifOgre::ObjectList objects = NifOgre::Loader::createObjects(mSkelBase, bonename, mInsert, model); - setRenderProperties(objects, (mViewMode == VM_FirstPerson) ? RV_FirstPerson : mVisibilityFlags, RQG_Main, RQG_Alpha); + setRenderProperties(objects, (mViewMode == VM_FirstPerson) ? RV_FirstPerson : mVisibilityFlags, RQG_Main, RQG_Alpha, 0, + enchantedGlow, glowColor); std::for_each(objects.mEntities.begin(), objects.mEntities.end(), SetObjectGroup(group)); std::for_each(objects.mParticles.begin(), objects.mParticles.end(), SetObjectGroup(group)); @@ -475,7 +478,7 @@ void NpcAnimation::removePartGroup(int group) } } -bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int group, int priority, const std::string &mesh) +bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int group, int priority, const std::string &mesh, bool enchantedGlow, Ogre::Vector3* glowColor) { if(priority <= mPartPriorities[type]) return false; @@ -484,7 +487,7 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g mPartslots[type] = group; mPartPriorities[type] = priority; - mObjectParts[type] = insertBoundedPart(mesh, group, sPartList.at(type)); + mObjectParts[type] = insertBoundedPart(mesh, group, sPartList.at(type), enchantedGlow, glowColor); if(mObjectParts[type].mSkelBase) { Ogre::SkeletonInstance *skel = mObjectParts[type].mSkelBase->getSkeleton(); @@ -521,7 +524,7 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g return true; } -void NpcAnimation::addPartGroup(int group, int priority, const std::vector &parts) +void NpcAnimation::addPartGroup(int group, int priority, const std::vector &parts, bool enchantedGlow, Ogre::Vector3* glowColor) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const MWWorld::Store &partStore = store.get(); @@ -559,7 +562,7 @@ void NpcAnimation::addPartGroup(int group, int priority, const std::vectormPart, group, priority, "meshes\\"+bodypart->mModel); + addOrReplaceIndividualPart((ESM::PartReferenceType)part->mPart, group, priority, "meshes\\"+bodypart->mModel, enchantedGlow, glowColor); else reserveIndividualPart((ESM::PartReferenceType)part->mPart, group, priority); } @@ -574,8 +577,10 @@ void NpcAnimation::showWeapons(bool showWeapon) MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); if(weapon != inv.end()) // special case for weapons { + Ogre::Vector3 glowColor = getEnchantmentColor(*weapon); std::string mesh = MWWorld::Class::get(*weapon).getModel(*weapon); - addOrReplaceIndividualPart(ESM::PRT_Weapon, MWWorld::InventoryStore::Slot_CarriedRight, 1, mesh); + addOrReplaceIndividualPart(ESM::PRT_Weapon, MWWorld::InventoryStore::Slot_CarriedRight, 1, + mesh, !weapon->getClass().getEnchantment(*weapon).empty(), &glowColor); } } else diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index becd014370..7116b6ef1c 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -51,14 +51,17 @@ private: void updateNpcBase(); - NifOgre::ObjectList insertBoundedPart(const std::string &model, int group, const std::string &bonename); + NifOgre::ObjectList insertBoundedPart(const std::string &model, int group, const std::string &bonename, + bool enchantedGlow, Ogre::Vector3* glowColor=NULL); void removeIndividualPart(ESM::PartReferenceType type); void reserveIndividualPart(ESM::PartReferenceType type, int group, int priority); - bool addOrReplaceIndividualPart(ESM::PartReferenceType type, int group, int priority, const std::string &mesh); + bool addOrReplaceIndividualPart(ESM::PartReferenceType type, int group, int priority, const std::string &mesh, + bool enchantedGlow=false, Ogre::Vector3* glowColor=NULL); void removePartGroup(int group); - void addPartGroup(int group, int priority, const std::vector &parts); + void addPartGroup(int group, int priority, const std::vector &parts, + bool enchantedGlow=false, Ogre::Vector3* glowColor=NULL); public: /** diff --git a/apps/openmw/mwrender/objects.hpp b/apps/openmw/mwrender/objects.hpp index 165f6551d3..022752fae9 100644 --- a/apps/openmw/mwrender/objects.hpp +++ b/apps/openmw/mwrender/objects.hpp @@ -33,6 +33,8 @@ class Objects{ void insertBegin(const MWWorld::Ptr& ptr); + + public: Objects(OEngine::Render::OgreRenderer &renderer) : mRenderer(renderer) diff --git a/components/esm/loadmgef.hpp b/components/esm/loadmgef.hpp index cc9cc180ee..77056b9ec6 100644 --- a/components/esm/loadmgef.hpp +++ b/components/esm/loadmgef.hpp @@ -49,8 +49,9 @@ struct MagicEffect int mSchool; // SpellSchool, see defs.hpp float mBaseCost; int mFlags; - // Properties of the fired magic 'ball' I think - int mRed, mBlue, mGreen; + // Glow color for enchanted items with this effect + int mRed, mGreen, mBlue; + // Properties of the fired magic 'ball' float mSpeed, mSize, mSizeCap; }; // 36 bytes diff --git a/extern/shiny/Main/Factory.hpp b/extern/shiny/Main/Factory.hpp index 97984609ea..7d52266b55 100644 --- a/extern/shiny/Main/Factory.hpp +++ b/extern/shiny/Main/Factory.hpp @@ -259,8 +259,9 @@ namespace sh Platform* mPlatform; MaterialInstance* findInstance (const std::string& name); + public: MaterialInstance* searchInstance (const std::string& name); - + private: /// @return was anything removed? bool removeCache (const std::string& pattern); diff --git a/extern/shiny/Platforms/Ogre/OgreTextureUnitState.cpp b/extern/shiny/Platforms/Ogre/OgreTextureUnitState.cpp index 54dda3523f..f215f4ab7d 100644 --- a/extern/shiny/Platforms/Ogre/OgreTextureUnitState.cpp +++ b/extern/shiny/Platforms/Ogre/OgreTextureUnitState.cpp @@ -1,5 +1,8 @@ #include "OgreTextureUnitState.hpp" +#include +#include + #include "OgrePass.hpp" #include "OgrePlatform.hpp" #include "OgreMaterialSerializer.hpp" @@ -28,6 +31,32 @@ namespace sh setTextureName (retrieveValue(value, context).get()); return true; } + else if (name == "anim_texture2") + { + std::string val = retrieveValue(value, context).get(); + std::vector tokens; + boost::split(tokens, val, boost::is_any_of(" ")); + assert(tokens.size() == 3); + std::string texture = tokens[0]; + int frames = boost::lexical_cast(tokens[1]); + float duration = boost::lexical_cast(tokens[2]); + + std::vector frameTextures; + for (int i=0; isetAnimatedTextureName(&frameTextures[0], frames, duration); + return true; + } else if (name == "create_in_ffp") return true; // handled elsewhere diff --git a/files/materials/objects.mat b/files/materials/objects.mat index 8f8734d629..10fb72740f 100644 --- a/files/materials/objects.mat +++ b/files/materials/objects.mat @@ -19,6 +19,8 @@ material openmw_objects_base alpha_rejection default transparent_sorting default polygon_mode default + env_map false + env_map_color 1 1 1 pass { @@ -33,6 +35,8 @@ material openmw_objects_base detailMapUVSet $detailMapUVSet emissiveMap $emissiveMap detailMap $detailMap + env_map $env_map + env_map_color $env_map_color } diffuse $diffuse @@ -75,6 +79,14 @@ material openmw_objects_base direct_texture $detailMap tex_coord_set $detailMapUVSet } + + texture_unit envMap + { + create_in_ffp $env_map + env_map spherical + anim_texture2 textures\magicitem\caust.dds 32 2 + colour_op add + } texture_unit shadowMap0 { diff --git a/files/materials/objects.shader b/files/materials/objects.shader index 36f92bfd91..9fc9fceaf0 100644 --- a/files/materials/objects.shader +++ b/files/materials/objects.shader @@ -30,6 +30,8 @@ #define VIEWPROJ_FIX @shGlobalSettingBool(viewproj_fix) +#define ENV_MAP @shPropertyBool(env_map) + #ifdef SH_VERTEX_SHADER // ------------------------------------- VERTEX --------------------------------------- @@ -61,7 +63,7 @@ shOutput(float3, tangentPassthrough) #endif -#if !VERTEX_LIGHTING +#if !VERTEX_LIGHTING || ENV_MAP shOutput(float3, normalPassthrough) #endif @@ -79,12 +81,15 @@ shOutput(float4, colourPassthrough) #endif +#if ENV_MAP || VERTEX_LIGHTING + shUniform(float4x4, worldView) @shAutoConstant(worldView, worldview_matrix) +#endif + #if VERTEX_LIGHTING shUniform(float4, lightPosition[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightPosition, light_position_view_space_array, @shGlobalSettingString(num_lights)) shUniform(float4, lightDiffuse[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightDiffuse, light_diffuse_colour_array, @shGlobalSettingString(num_lights)) shUniform(float4, lightAttenuation[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightAttenuation, light_attenuation_array, @shGlobalSettingString(num_lights)) shUniform(float4, lightAmbient) @shAutoConstant(lightAmbient, ambient_light_colour) - shUniform(float4x4, worldView) @shAutoConstant(worldView, worldview_matrix) #if VERTEXCOLOR_MODE != 2 shUniform(float4, materialAmbient) @shAutoConstant(materialAmbient, surface_ambient_colour) #endif @@ -125,10 +130,23 @@ UV.zw = uv1; #endif +#if ENV_MAP || VERTEX_LIGHTING + float3 viewNormal = normalize(shMatrixMult(worldView, float4(normal.xyz, 0)).xyz); +#endif + +#if ENV_MAP + float3 viewVec = normalize( shMatrixMult(worldView, shInputPosition).xyz); + + float3 r = reflect( viewVec, viewNormal ); + float m = 2.0 * sqrt( r.x*r.x + r.y*r.y + (r.z+1.0)*(r.z+1.0) ); + UV.z = r.x/m + 0.5; + UV.w = r.y/m + 0.5; +#endif + #if NORMAL_MAP tangentPassthrough = tangent.xyz; #endif -#if !VERTEX_LIGHTING +#if !VERTEX_LIGHTING || ENV_MAP normalPassthrough = normal.xyz; #endif #if VERTEXCOLOR_MODE != 0 && !VERTEX_LIGHTING @@ -173,7 +191,6 @@ #if VERTEX_LIGHTING float3 viewPos = shMatrixMult(worldView, shInputPosition).xyz; - float3 viewNormal = normalize(shMatrixMult(worldView, float4(normal.xyz, 0)).xyz); float3 lightDir; float d; @@ -242,12 +259,18 @@ shSampler2D(detailMap) #endif +#if ENV_MAP + shSampler2D(envMap) + shUniform(float3, env_map_color) @shUniformProperty3f(env_map_color, env_map_color) + shUniform(float3, cameraPosObjSpace) @shAutoConstant(cameraPosObjSpace, camera_position_object_space) +#endif + shInput(float4, UV) #if NORMAL_MAP shInput(float3, tangentPassthrough) #endif -#if !VERTEX_LIGHTING +#if !VERTEX_LIGHTING || ENV_MAP shInput(float3, normalPassthrough) #endif @@ -327,8 +350,11 @@ #endif #endif -#if NORMAL_MAP +#if !VERTEX_LIGHTING || ENV_MAP float3 normal = normalPassthrough; +#endif + +#if NORMAL_MAP float3 binormal = cross(tangentPassthrough.xyz, normal.xyz); float3x3 tbn = float3x3(tangentPassthrough.xyz, binormal, normal.xyz); @@ -420,6 +446,23 @@ shOutputColour(0) *= lightResult; #endif +#if EMISSIVE_MAP + #if @shPropertyString(emissiveMapUVSet) + shOutputColour(0).xyz += shSample(emissiveMap, UV.zw).xyz; + #else + shOutputColour(0).xyz += shSample(emissiveMap, UV.xy).xyz; + #endif +#endif + +#if ENV_MAP + // Everything looks better with fresnel + float3 eyeDir = normalize(cameraPosObjSpace.xyz - objSpacePositionPassthrough.xyz); + float facing = 1.0 - max(abs(dot(-eyeDir, normal)), 0); + float envFactor = shSaturate(0.25 + 0.75 * pow(facing, 1)); + + shOutputColour(0).xyz += shSample(envMap, UV.zw).xyz * envFactor * env_map_color; +#endif + #if FOG float fogValue = shSaturate((depthPassthrough - fogParams.y) * fogParams.w); @@ -430,14 +473,6 @@ shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, fogColour, fogValue); #endif -#endif - -#if EMISSIVE_MAP - #if @shPropertyString(emissiveMapUVSet) - shOutputColour(0).xyz += shSample(emissiveMap, UV.zw).xyz; - #else - shOutputColour(0).xyz += shSample(emissiveMap, UV.xy).xyz; - #endif #endif // prevent negative colour output (for example with negative lights) From fa639248848eb0d6a831c2c052f7cabb352f0355 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 20 Nov 2013 05:49:05 +0100 Subject: [PATCH 323/434] Simplify message box manager, should fix a random bug with boxes not disappearing --- apps/openmw/mwgui/messagebox.cpp | 71 +++++++++----------------------- apps/openmw/mwgui/messagebox.hpp | 12 +----- 2 files changed, 22 insertions(+), 61 deletions(-) diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index 48d7ec1717..0a9ee01888 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -10,7 +10,6 @@ namespace MWGui MessageBoxManager::MessageBoxManager () { - // defines mMessageBoxSpeed = 0.1; mInterMessageBoxe = NULL; mStaticMessageBox = NULL; @@ -19,47 +18,26 @@ namespace MWGui void MessageBoxManager::onFrame (float frameDuration) { - std::vector::iterator it; - for(it = mTimers.begin(); it != mTimers.end();) + std::vector::iterator it; + for(it = mMessageBoxes.begin(); it != mMessageBoxes.end();) { - // if this messagebox is already deleted, remove the timer and move on - if (std::find(mMessageBoxes.begin(), mMessageBoxes.end(), it->messageBox) == mMessageBoxes.end()) + (*it)->mCurrentTime += frameDuration; + if((*it)->mCurrentTime >= (*it)->mMaxTime && *it != mStaticMessageBox) { - it = mTimers.erase(it); - continue; - } - - it->current += frameDuration; - if(it->current >= it->max) - { - it->messageBox->mMarkedToDelete = true; - - if(*mMessageBoxes.begin() == it->messageBox) // if this box is the last one - { - // collect all with mMarkedToDelete and delete them. - // and place the other messageboxes on the right position - int height = 0; - std::vector::iterator it2 = mMessageBoxes.begin(); - while(it2 != mMessageBoxes.end()) - { - if((*it2)->mMarkedToDelete) - { - delete (*it2); - it2 = mMessageBoxes.erase(it2); - } - else { - (*it2)->update(height); - height += (*it2)->getHeight(); - ++it2; - } - } - } - it = mTimers.erase(it); + delete *it; + it = mMessageBoxes.erase(it); } else - { ++it; - } + } + + float height = 0; + it = mMessageBoxes.begin(); + while(it != mMessageBoxes.end()) + { + (*it)->update(height); + height += (*it)->getHeight(); + ++it; } if(mInterMessageBoxe != NULL && mInterMessageBoxe->mMarkedToDelete) { @@ -74,14 +52,13 @@ namespace MWGui void MessageBoxManager::createMessageBox (const std::string& message, bool stat) { MessageBox *box = new MessageBox(*this, message); + box->mCurrentTime = 0; + box->mMaxTime = message.length()*mMessageBoxSpeed; if(stat) mStaticMessageBox = box; - else - removeMessageBox(message.length()*mMessageBoxSpeed, box); mMessageBoxes.push_back(box); - std::vector::iterator it; if(mMessageBoxes.size() > 3) { delete *mMessageBoxes.begin(); @@ -89,7 +66,7 @@ namespace MWGui } int height = 0; - for(it = mMessageBoxes.begin(); it != mMessageBoxes.end(); ++it) + for(std::vector::iterator it = mMessageBoxes.begin(); it != mMessageBoxes.end(); ++it) { (*it)->update(height); height += (*it)->getHeight(); @@ -119,15 +96,6 @@ namespace MWGui return mInterMessageBoxe != NULL; } - void MessageBoxManager::removeMessageBox (float time, MessageBox *msgbox) - { - MessageBoxManagerTimer timer; - timer.current = 0; - timer.max = time; - timer.messageBox = msgbox; - - mTimers.insert(mTimers.end(), timer); - } bool MessageBoxManager::removeMessageBox (MessageBox *msgbox) { @@ -169,12 +137,13 @@ namespace MWGui : Layout("openmw_messagebox.layout") , mMessageBoxManager(parMessageBoxManager) , mMessage(message) + , mCurrentTime(0) + , mMaxTime(0) { // defines mFixedWidth = 300; mBottomPadding = 20; mNextBoxPadding = 20; - mMarkedToDelete = false; getWidget(mMessageWidget, "message"); diff --git a/apps/openmw/mwgui/messagebox.hpp b/apps/openmw/mwgui/messagebox.hpp index 63840cfe2c..ce3a85ab3e 100644 --- a/apps/openmw/mwgui/messagebox.hpp +++ b/apps/openmw/mwgui/messagebox.hpp @@ -19,13 +19,6 @@ namespace MWGui class InteractiveMessageBox; class MessageBoxManager; class MessageBox; - - struct MessageBoxManagerTimer { - float current; - float max; - MessageBox *messageBox; - }; - class MessageBoxManager { public: @@ -36,7 +29,6 @@ namespace MWGui bool createInteractiveMessageBox (const std::string& message, const std::vector& buttons); bool isInteractiveMessageBox (); - void removeMessageBox (float time, MessageBox *msgbox); bool removeMessageBox (MessageBox *msgbox); void setMessageBoxSpeed (int speed); @@ -54,7 +46,6 @@ namespace MWGui std::vector mMessageBoxes; InteractiveMessageBox* mInterMessageBoxe; MessageBox* mStaticMessageBox; - std::vector mTimers; float mMessageBoxSpeed; int mLastButtonPressed; }; @@ -67,7 +58,8 @@ namespace MWGui int getHeight (); void update (int height); - bool mMarkedToDelete; + float mCurrentTime; + float mMaxTime; protected: MessageBoxManager& mMessageBoxManager; From 5a4bd9b202824baba15b9498aee2bfef11adfb7b Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 20 Nov 2013 06:20:16 +0100 Subject: [PATCH 324/434] Remove software cursor option, hw cursors seem to be working well enough --- apps/openmw/mwgui/cursor.cpp | 53 -------------------------- apps/openmw/mwgui/cursor.hpp | 17 --------- apps/openmw/mwgui/windowmanagerimp.cpp | 24 +++--------- apps/openmw/mwgui/windowmanagerimp.hpp | 3 -- 4 files changed, 5 insertions(+), 92 deletions(-) diff --git a/apps/openmw/mwgui/cursor.cpp b/apps/openmw/mwgui/cursor.cpp index c069eca15a..9c64f94caa 100644 --- a/apps/openmw/mwgui/cursor.cpp +++ b/apps/openmw/mwgui/cursor.cpp @@ -73,57 +73,4 @@ namespace MWGui return mSize; } - // ---------------------------------------------------------------------------------------- - - Cursor::Cursor() - { - // hide mygui's pointer since we're rendering it ourselves (because mygui's pointer doesn't support rotation) - MyGUI::PointerManager::getInstance().setVisible(false); - - MyGUI::PointerManager::getInstance().eventChangeMousePointer += MyGUI::newDelegate(this, &Cursor::onCursorChange); - - mWidget = MyGUI::Gui::getInstance().createWidget("RotatingSkin",0,0,0,0,MyGUI::Align::Default,"Pointer",""); - - onCursorChange(MyGUI::PointerManager::getInstance().getDefaultPointer()); - } - - Cursor::~Cursor() - { - } - - void Cursor::onCursorChange(const std::string &name) - { - ResourceImageSetPointerFix* imgSetPtr = dynamic_cast( - MyGUI::PointerManager::getInstance().getByName(name)); - assert(imgSetPtr != NULL); - - MyGUI::ResourceImageSet* imgSet = imgSetPtr->getImageSet(); - - std::string texture = imgSet->getIndexInfo(0,0).texture; - - mSize = imgSetPtr->getSize(); - mHotSpot = imgSetPtr->getHotSpot(); - - int rotation = imgSetPtr->getRotation(); - - mWidget->setImageTexture(texture); - MyGUI::ISubWidget* main = mWidget->getSubWidgetMain(); - MyGUI::RotatingSkin* rotatingSubskin = main->castType(); - rotatingSubskin->setCenter(MyGUI::IntPoint(mSize.width/2,mSize.height/2)); - rotatingSubskin->setAngle(Ogre::Degree(rotation).valueRadians()); - } - - void Cursor::update() - { - MyGUI::IntPoint position = MyGUI::InputManager::getInstance().getMousePosition(); - - mWidget->setPosition(position - mHotSpot); - mWidget->setSize(mSize); - } - - void Cursor::setVisible(bool visible) - { - mWidget->setVisible(visible); - } - } diff --git a/apps/openmw/mwgui/cursor.hpp b/apps/openmw/mwgui/cursor.hpp index badf82262b..4e3eb90972 100644 --- a/apps/openmw/mwgui/cursor.hpp +++ b/apps/openmw/mwgui/cursor.hpp @@ -39,23 +39,6 @@ namespace MWGui int mRotation; // rotation in degrees }; - class Cursor - { - public: - Cursor(); - ~Cursor(); - void update (); - - void setVisible (bool visible); - - void onCursorChange (const std::string& name); - - private: - MyGUI::ImageBox* mWidget; - - MyGUI::IntSize mSize; - MyGUI::IntPoint mHotSpot; - }; } #endif diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index b77d1a885b..b5447eaa85 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -100,7 +100,6 @@ namespace MWGui , mRepair(NULL) , mCompanionWindow(NULL) , mTranslationDataStorage (translationDataStorage) - , mSoftwareCursor(NULL) , mCharGen(NULL) , mInputBlocker(NULL) , mCrosshairEnabled(Settings::Manager::getBool ("crosshair", "HUD")) @@ -128,7 +127,6 @@ namespace MWGui , mFPS(0.0f) , mTriangleCount(0) , mBatchCount(0) - , mUseHardwareCursors(Settings::Manager::getBool("hardware cursors", "GUI")) { // Set up the GUI system mGuiManager = new OEngine::GUI::MyGUIManager(mRendering->getWindow(), mRendering->getScene(), false, logpath); @@ -173,16 +171,19 @@ namespace MWGui mLoadingScreen->onResChange (w,h); //set up the hardware cursor manager - mSoftwareCursor = new Cursor(); mCursorManager = new SFO::SDLCursorManager(); MyGUI::PointerManager::getInstance().eventChangeMousePointer += MyGUI::newDelegate(this, &WindowManager::onCursorChange); MyGUI::InputManager::getInstance().eventChangeKeyFocus += MyGUI::newDelegate(this, &WindowManager::onKeyFocusChanged); - setUseHardwareCursors(mUseHardwareCursors); + mCursorManager->setEnabled(true); + onCursorChange(MyGUI::PointerManager::getInstance().getDefaultPointer()); mCursorManager->cursorVisibilityChange(false); + + // hide mygui's pointer + MyGUI::PointerManager::getInstance().setVisible(false); } void WindowManager::initUI() @@ -313,7 +314,6 @@ namespace MWGui delete mMerchantRepair; delete mRepair; delete mSoulgemDialog; - delete mSoftwareCursor; delete mCursorManager; delete mRecharge; @@ -344,8 +344,6 @@ namespace MWGui mHud->setBatchCount(mBatchCount); mHud->update(); - - mSoftwareCursor->update(); } void WindowManager::updateVisible() @@ -879,12 +877,6 @@ namespace MWGui MWBase::Environment::get().getInputManager()->setDragDrop(dragDrop); } - void WindowManager::setUseHardwareCursors(bool use) - { - mCursorManager->setEnabled(use); - mSoftwareCursor->setVisible(!use && mCursorVisible); - } - void WindowManager::setCursorVisible(bool visible) { if(mCursorVisible == visible) @@ -892,8 +884,6 @@ namespace MWGui mCursorVisible = visible; mCursorManager->cursorVisibilityChange(visible); - - mSoftwareCursor->setVisible(!mUseHardwareCursors && visible); } void WindowManager::onRetrieveTag(const MyGUI::UString& _tag, MyGUI::UString& _result) @@ -924,8 +914,6 @@ namespace MWGui mHud->setFpsLevel(Settings::Manager::getInt("fps", "HUD")); mToolTips->setDelay(Settings::Manager::getFloat("tooltip delay", "GUI")); - setUseHardwareCursors(Settings::Manager::getBool("hardware cursors", "GUI")); - for (Settings::CategorySettingVector::const_iterator it = changed.begin(); it != changed.end(); ++it) { @@ -977,8 +965,6 @@ namespace MWGui void WindowManager::onCursorChange(const std::string &name) { - mSoftwareCursor->onCursorChange(name); - if(!mCursorManager->cursorChanged(name)) return; //the cursor manager doesn't want any more info about this cursor //See if we can get the information we need out of the cursor resource diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 965c7d6382..5231397cb6 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -369,9 +369,6 @@ namespace MWGui unsigned int mTriangleCount; unsigned int mBatchCount; - bool mUseHardwareCursors; - void setUseHardwareCursors(bool use); - /** * Called when MyGUI tries to retrieve a tag. This usually corresponds to a GMST string, * so this method will retrieve the GMST with the name \a _tag and place the result in \a _result From 862f2f0883a0083abae305e11e319e35ffcb8219 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Wed, 20 Nov 2013 10:16:51 +0100 Subject: [PATCH 325/434] added keywords for debian and switch to dyn libs in travis --- .travis.yml | 6 +++--- cmake/FindOGRE.cmake | 6 ++++++ files/opencs.desktop | 1 + files/openmw.desktop | 1 + 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index caf2e33899..0fb8bf71ae 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,13 +9,13 @@ before_install: - pwd - git submodule update --init --recursive - echo "yes" | sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu `lsb_release -sc` main universe restricted multiverse" - - echo "yes" | sudo apt-add-repository ppa:openmw/deps + - echo "yes" | sudo apt-add-repository ppa:openmw/openmw - sudo apt-get update -qq - sudo apt-get install -qq libboost-all-dev libgtest-dev google-mock libzzip-dev - sudo apt-get install -qq libqt4-dev libxaw7-dev libxrandr-dev libfreeimage-dev libpng-dev - sudo apt-get install -qq libopenal-dev libmpg123-dev libsndfile1-dev - sudo apt-get install -qq libavcodec-dev libavformat-dev libavdevice-dev libavutil-dev libswscale-dev libpostproc-dev - - sudo apt-get install -qq libbullet-dev libogre-static-dev libmygui-static-dev libsdl2-static-dev libunshield-dev + - sudo apt-get install -qq libbullet-dev libogre-1.8-dev libmygui-dev libsdl2-dev libunshield-dev - sudo mkdir /usr/src/gtest/build - cd /usr/src/gtest/build - sudo cmake .. -DBUILD_SHARED_LIBS=1 @@ -26,7 +26,7 @@ before_script: - cd - - mkdir build - cd build - - cmake .. -DOGRE_STATIC=1 -DMYGUI_STATIC=1 -DBOOST_STATIC=1 -DSDL2_STATIC=1 -DBUILD_WITH_CODE_COVERAGE=1 -DBUILD_UNITTESTS=1 + - cmake .. -DBUILD_WITH_CODE_COVERAGE=1 -DBUILD_UNITTESTS=1 DBUILD_WITH_DPKG=1 script: - make -j4 after_script: diff --git a/cmake/FindOGRE.cmake b/cmake/FindOGRE.cmake index fb4a090c6e..6a3ffd6eda 100644 --- a/cmake/FindOGRE.cmake +++ b/cmake/FindOGRE.cmake @@ -552,3 +552,9 @@ clear_if_changed(OGRE_PREFIX_WATCH OGRE_MEDIA_DIR) find_path(OGRE_MEDIA_DIR NAMES packs/cubemapsJS.zip HINTS ${OGRE_MEDIA_SEARCH_PATH} PATHS ${OGRE_PREFIX_PATH} PATH_SUFFIXES ${OGRE_MEDIA_SEARCH_SUFFIX}) +if (OGRE_RenderSystem_GL_FOUND) +MESSAGE("-- OpenGL -- Found!") +else() +MESSAGE("-- OpenGL -- Not Found!") +endif() +MESSAGE("-- ${OGRE_RenderSystem_GL_LIBRARIES} ${OPENGL_LIBRARIES}") diff --git a/files/opencs.desktop b/files/opencs.desktop index f6aad5b097..80afa26bce 100644 --- a/files/opencs.desktop +++ b/files/opencs.desktop @@ -3,6 +3,7 @@ Type=Application Name=OpenMW Content Editor GenericName=Content Editor Comment=A replacement for the Morrowind Construction Set. +Keywords=Morrowind;Construction Set;Creation Kit Editor;Set;Kit TryExec=opencs Exec=opencs Icon=opencs diff --git a/files/openmw.desktop b/files/openmw.desktop index 118cd3bbe6..3e26018d0c 100644 --- a/files/openmw.desktop +++ b/files/openmw.desktop @@ -3,6 +3,7 @@ Type=Application Name=OpenMW Launcher GenericName=Role Playing Game Comment=An engine replacement for The Elder Scrolls III: Morrowind +Keywords=Morrowind;Reimplementation Mods;esm;bsa TryExec=omwlauncher Exec=omwlauncher Icon=openmw From 729ca1bfddfadb33921d40063c3a63d8cfd30e4a Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Wed, 20 Nov 2013 10:18:49 +0100 Subject: [PATCH 326/434] remove debug code --- cmake/FindOGRE.cmake | 7 ------- 1 file changed, 7 deletions(-) diff --git a/cmake/FindOGRE.cmake b/cmake/FindOGRE.cmake index 6a3ffd6eda..96f93cf34a 100644 --- a/cmake/FindOGRE.cmake +++ b/cmake/FindOGRE.cmake @@ -551,10 +551,3 @@ set(OGRE_MEDIA_SEARCH_SUFFIX clear_if_changed(OGRE_PREFIX_WATCH OGRE_MEDIA_DIR) find_path(OGRE_MEDIA_DIR NAMES packs/cubemapsJS.zip HINTS ${OGRE_MEDIA_SEARCH_PATH} PATHS ${OGRE_PREFIX_PATH} PATH_SUFFIXES ${OGRE_MEDIA_SEARCH_SUFFIX}) - -if (OGRE_RenderSystem_GL_FOUND) -MESSAGE("-- OpenGL -- Found!") -else() -MESSAGE("-- OpenGL -- Not Found!") -endif() -MESSAGE("-- ${OGRE_RenderSystem_GL_LIBRARIES} ${OPENGL_LIBRARIES}") From ac38274165c78fe73193aefaa3773f25529a3798 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Wed, 20 Nov 2013 10:23:23 +0100 Subject: [PATCH 327/434] typo in travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0fb8bf71ae..eaf83f8bc7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,7 +26,7 @@ before_script: - cd - - mkdir build - cd build - - cmake .. -DBUILD_WITH_CODE_COVERAGE=1 -DBUILD_UNITTESTS=1 DBUILD_WITH_DPKG=1 + - cmake .. -DBUILD_WITH_CODE_COVERAGE=1 -DBUILD_UNITTESTS=1 -DBUILD_WITH_DPKG=1 script: - make -j4 after_script: From 995c01d7ba7982dace8aa37c9adbe34aa482ff24 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Wed, 20 Nov 2013 10:29:22 +0100 Subject: [PATCH 328/434] added uuid-dev which is now required during building --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index eaf83f8bc7..112cf94f16 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ before_install: - echo "yes" | sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu `lsb_release -sc` main universe restricted multiverse" - echo "yes" | sudo apt-add-repository ppa:openmw/openmw - sudo apt-get update -qq - - sudo apt-get install -qq libboost-all-dev libgtest-dev google-mock libzzip-dev + - sudo apt-get install -qq libboost-all-dev libgtest-dev google-mock libzzip-dev uuid-dev - sudo apt-get install -qq libqt4-dev libxaw7-dev libxrandr-dev libfreeimage-dev libpng-dev - sudo apt-get install -qq libopenal-dev libmpg123-dev libsndfile1-dev - sudo apt-get install -qq libavcodec-dev libavformat-dev libavdevice-dev libavutil-dev libswscale-dev libpostproc-dev From 7f735c2c4c099da209c677c27ef4d25cf5d94422 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 20 Nov 2013 16:05:24 +0100 Subject: [PATCH 329/434] Release and show the cursor when focus lost --- apps/openmw/mwbase/windowmanager.hpp | 3 ++ apps/openmw/mwgui/windowmanagerimp.cpp | 11 ++--- apps/openmw/mwgui/windowmanagerimp.hpp | 2 + apps/openmw/mwinput/inputmanagerimp.cpp | 2 + extern/sdl4ogre/cursormanager.hpp | 3 -- extern/sdl4ogre/sdlcursormanager.cpp | 23 +--------- extern/sdl4ogre/sdlcursormanager.hpp | 3 -- extern/sdl4ogre/sdlinputwrapper.cpp | 56 +++++++++++++++++++------ extern/sdl4ogre/sdlinputwrapper.hpp | 10 ++++- 9 files changed, 66 insertions(+), 47 deletions(-) diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index c6864de369..c47ad066b5 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -284,6 +284,9 @@ namespace MWBase virtual void setKeyFocusWidget (MyGUI::Widget* widget) = 0; virtual Loading::Listener* getLoadingScreen() = 0; + + /// Should the cursor be visible? + virtual bool getCursorVisible() = 0; }; } diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index b5447eaa85..dc753a8fa7 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -180,7 +180,7 @@ namespace MWGui mCursorManager->setEnabled(true); onCursorChange(MyGUI::PointerManager::getInstance().getDefaultPointer()); - mCursorManager->cursorVisibilityChange(false); + SDL_ShowCursor(false); // hide mygui's pointer MyGUI::PointerManager::getInstance().setVisible(false); @@ -879,11 +879,7 @@ namespace MWGui void WindowManager::setCursorVisible(bool visible) { - if(mCursorVisible == visible) - return; - mCursorVisible = visible; - mCursorManager->cursorVisibilityChange(visible); } void WindowManager::onRetrieveTag(const MyGUI::UString& _tag, MyGUI::UString& _result) @@ -1362,4 +1358,9 @@ namespace MWGui mRecharge->start(soulgem); } + bool WindowManager::getCursorVisible() + { + return mCursorVisible; + } + } diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 5231397cb6..c5b6ff2beb 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -278,6 +278,8 @@ namespace MWGui void onSoulgemDialogButtonPressed (int button); + virtual bool getCursorVisible(); + private: bool mConsoleOnlyScripts; diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 9b50be7cab..6901bcdb92 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -264,6 +264,8 @@ namespace MWInput void InputManager::update(float dt, bool loading) { + mInputManager->setMouseVisible(MWBase::Environment::get().getWindowManager()->getCursorVisible()); + mInputManager->capture(loading); // inject some fake mouse movement to force updating MyGUI's widget states // this shouldn't do any harm since we're moving back to the original position afterwards diff --git a/extern/sdl4ogre/cursormanager.hpp b/extern/sdl4ogre/cursormanager.hpp index f45c5cdc24..35ec92a706 100644 --- a/extern/sdl4ogre/cursormanager.hpp +++ b/extern/sdl4ogre/cursormanager.hpp @@ -22,9 +22,6 @@ public: /// \brief Follow up a cursorChanged() call with enough info to create an cursor. virtual void receiveCursorInfo(const std::string &name, int rotDegrees, Ogre::TexturePtr tex, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y) = 0; - /// \brief Tell the manager when the cursor visibility changed - virtual void cursorVisibilityChange(bool visible) = 0; - /// \brief sets whether to actively manage cursors or not virtual void setEnabled(bool enabled) = 0; }; diff --git a/extern/sdl4ogre/sdlcursormanager.cpp b/extern/sdl4ogre/sdlcursormanager.cpp index d14a9ffa09..65fb7f98bc 100644 --- a/extern/sdl4ogre/sdlcursormanager.cpp +++ b/extern/sdl4ogre/sdlcursormanager.cpp @@ -10,7 +10,6 @@ namespace SFO SDLCursorManager::SDLCursorManager() : mEnabled(false), - mCursorVisible(false), mInitialized(false) { } @@ -70,27 +69,7 @@ namespace SFO void SDLCursorManager::_setGUICursor(const std::string &name) { - if(mEnabled && mCursorVisible) - { - SDL_SetCursor(mCursorMap.find(name)->second); - _setCursorVisible(mCursorVisible); - } - } - - void SDLCursorManager::_setCursorVisible(bool visible) - { - if(!mEnabled) - return; - - SDL_ShowCursor(visible ? SDL_TRUE : SDL_FALSE); - } - - void SDLCursorManager::cursorVisibilityChange(bool visible) - { - mCursorVisible = visible; - - _setGUICursor(mCurrentCursor); - _setCursorVisible(visible); + SDL_SetCursor(mCursorMap.find(name)->second); } void SDLCursorManager::receiveCursorInfo(const std::string& name, int rotDegrees, Ogre::TexturePtr tex, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y) diff --git a/extern/sdl4ogre/sdlcursormanager.hpp b/extern/sdl4ogre/sdlcursormanager.hpp index 8940220d41..7ba69f013e 100644 --- a/extern/sdl4ogre/sdlcursormanager.hpp +++ b/extern/sdl4ogre/sdlcursormanager.hpp @@ -19,14 +19,12 @@ namespace SFO virtual bool cursorChanged(const std::string &name); virtual void receiveCursorInfo(const std::string &name, int rotDegrees, Ogre::TexturePtr tex, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y); - virtual void cursorVisibilityChange(bool visible); private: void _createCursorFromResource(const std::string &name, int rotDegrees, Ogre::TexturePtr tex, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y); void _putPixel(SDL_Surface *surface, int x, int y, Uint32 pixel); void _setGUICursor(const std::string& name); - void _setCursorVisible(bool visible); typedef std::map CursorMap; CursorMap mCursorMap; @@ -34,7 +32,6 @@ namespace SFO std::string mCurrentCursor; bool mEnabled; bool mInitialized; - bool mCursorVisible; }; } diff --git a/extern/sdl4ogre/sdlinputwrapper.cpp b/extern/sdl4ogre/sdlinputwrapper.cpp index 5c0eff1c0f..9990e828aa 100644 --- a/extern/sdl4ogre/sdlinputwrapper.cpp +++ b/extern/sdl4ogre/sdlinputwrapper.cpp @@ -23,7 +23,11 @@ namespace SFO mJoyListener(NULL), mKeyboardListener(NULL), mMouseListener(NULL), - mWindowListener(NULL) + mWindowListener(NULL), + mWindowHasFocus(true), + mWantGrab(false), + mWantRelative(false), + mWantMouseVisible(false) { _setupOISKeys(); } @@ -51,13 +55,16 @@ namespace SFO switch(evt.type) { case SDL_MOUSEMOTION: - //ignore this if it happened due to a warp + // Ignore this if it happened due to a warp if(!_handleWarpMotion(evt.motion)) { - mMouseListener->mouseMoved(_packageMouseMotion(evt)); + // If in relative mode, don't trigger events unless window has focus + if (!mWantRelative || mWindowHasFocus) + mMouseListener->mouseMoved(_packageMouseMotion(evt)); - //try to keep the mouse inside the window - _wrapMousePointer(evt.motion); + // Try to keep the mouse inside the window + if (mWindowHasFocus) + _wrapMousePointer(evt.motion); } break; case SDL_MOUSEWHEEL: @@ -118,11 +125,11 @@ namespace SFO switch (evt.window.event) { case SDL_WINDOWEVENT_ENTER: mMouseInWindow = true; + updateMouseSettings(); break; case SDL_WINDOWEVENT_LEAVE: mMouseInWindow = false; - SDL_SetWindowGrab(mSDLWindow, SDL_FALSE); - SDL_SetRelativeMouseMode(SDL_FALSE); + updateMouseSettings(); break; case SDL_WINDOWEVENT_SIZE_CHANGED: int w,h; @@ -149,10 +156,15 @@ namespace SFO break; case SDL_WINDOWEVENT_FOCUS_GAINED: + mWindowHasFocus = true; + updateMouseSettings(); if (mWindowListener) mWindowListener->windowFocusChange(true); + break; case SDL_WINDOWEVENT_FOCUS_LOST: + mWindowHasFocus = false; + updateMouseSettings(); if (mWindowListener) mWindowListener->windowFocusChange(false); break; @@ -193,25 +205,43 @@ namespace SFO /// \brief Locks the pointer to the window void InputWrapper::setGrabPointer(bool grab) { - mGrabPointer = grab && mMouseInWindow; - SDL_SetWindowGrab(mSDLWindow, grab && mMouseInWindow ? SDL_TRUE : SDL_FALSE); + mWantGrab = grab; + updateMouseSettings(); } /// \brief Set the mouse to relative positioning. Doesn't move the cursor /// and disables mouse acceleration. void InputWrapper::setMouseRelative(bool relative) { - if(mMouseRelative == relative && mMouseInWindow) + mWantRelative = relative; + updateMouseSettings(); + } + + void InputWrapper::setMouseVisible(bool visible) + { + mWantMouseVisible = visible; + updateMouseSettings(); + } + + void InputWrapper::updateMouseSettings() + { + mGrabPointer = mWantGrab && mMouseInWindow && mWindowHasFocus; + SDL_SetWindowGrab(mSDLWindow, mGrabPointer ? SDL_TRUE : SDL_FALSE); + + SDL_ShowCursor(mWantMouseVisible || !mWindowHasFocus); + + bool relative = mWantRelative && mMouseInWindow && mWindowHasFocus; + if(mMouseRelative == relative) return; - mMouseRelative = relative && mMouseInWindow; + mMouseRelative = relative; mWrapPointer = false; //eep, wrap the pointer manually if the input driver doesn't support //relative positioning natively - int success = SDL_SetRelativeMouseMode(relative && mMouseInWindow ? SDL_TRUE : SDL_FALSE); - if(relative && mMouseInWindow && success != 0) + int success = SDL_SetRelativeMouseMode(relative ? SDL_TRUE : SDL_FALSE); + if(relative && success != 0) mWrapPointer = true; //now remove all mouse events using the old setting from the queue diff --git a/extern/sdl4ogre/sdlinputwrapper.hpp b/extern/sdl4ogre/sdlinputwrapper.hpp index 1bd8947a0d..ca57464e92 100644 --- a/extern/sdl4ogre/sdlinputwrapper.hpp +++ b/extern/sdl4ogre/sdlinputwrapper.hpp @@ -28,6 +28,7 @@ namespace SFO bool isModifierHeld(SDL_Keymod mod); bool isKeyDown(SDL_Scancode key); + void setMouseVisible (bool visible); void setMouseRelative(bool relative); bool getMouseRelative() { return mMouseRelative; } void setGrabPointer(bool grab); @@ -36,6 +37,8 @@ namespace SFO void warpMouse(int x, int y); + void updateMouseSettings(); + private: void handleWindowEvent(const SDL_Event& evt); @@ -57,14 +60,19 @@ namespace SFO Uint16 mWarpX; Uint16 mWarpY; bool mWarpCompensate; - bool mMouseRelative; bool mWrapPointer; + + bool mWantMouseVisible; + bool mWantGrab; + bool mWantRelative; bool mGrabPointer; + bool mMouseRelative; Sint32 mMouseZ; Sint32 mMouseX; Sint32 mMouseY; + bool mWindowHasFocus; bool mMouseInWindow; SDL_Window* mSDLWindow; From 000606efc02c28cc8bad6636af407aa34a6b5095 Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Wed, 20 Nov 2013 20:05:22 +0100 Subject: [PATCH 330/434] =?UTF-8?q?working,=20working=E2=80=A6=20it=20take?= =?UTF-8?q?s=20ages.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- manual/opencs/files_and_directories.tex | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/manual/opencs/files_and_directories.tex b/manual/opencs/files_and_directories.tex index e88018af2f..307a5565f8 100644 --- a/manual/opencs/files_and_directories.tex +++ b/manual/opencs/files_and_directories.tex @@ -12,14 +12,20 @@ Needless to say, this chapter describes paths and files that are used/created/ed Open{CS} user should consider two directory paths in his system. So called ``User Configuration Path'' and ``Data Path''. ``User Configuration Path'' stores basically all Open{MW} specific files that are supposed to be touched by the user, while ``Data Path''%This is wrong, pay no mind to it. \paragraph{Content files} -Bethesda Morrowind engine is using two types of files: esm (master) and esp (plugin). The distinction between those is not clear, and often confusing, mostly because large esp files could hurt stability. Open{MW} supports both esm and esp files, but in order to make use of new features of OpenMW one should consider using new file types designed with our engine in mind: game files and addon files.\\ +Bethesda Morrowind engine is using two types of files: esm (master) and esp (plugin). The distinction between those is not clear, and often confusing, mostly because large esp files could hurt stability. Open{MW} supports both esm and esp files, but in order to make use of new features of OpenMW one should consider using new file types designed with our engine in mind: game files and addon files together called ``content files``.\\ -Game and Addon files are concept descended from old esm/esp system, but much more flexible and cleaner. Finally, We can describe the difference between those two file types use case with pure statements. If you want to make new game using Open{MW} as engine (so called ``total conversion'') you should create a game file. If you want to create a addon for existing game file -- simply create addon file. The addon size is not a factor here. The only distinction you should consider is if your project is about changing other game, or creating a new one. Simple as that. +Game and Addon files are concept descended from old esm/esp system, but much more flexible and cleaner. Finally, We can describe the difference between those two file types use case with pure statements. If you want to make new game using Open{MW} as engine (so called ``total conversion'') you should create a game file. If you want to create a addon for existing game file -- simply create addon file. The addon size is not a factor here. The only distinction you should consider is if your project is about changing other game, or creating a new one. Simple as that.\\ %TODO describe some characteristics, like file extensions. What about project files? The actual creating of new files is described in the next chapter. Here We are gonna focus only on details that you need to know in order to create your first Open{CS} file while full understanding your needs. +\paragraph{Project files} +Project files act as containers for data not used by the Openm{MW} game engine itself, but still useful for OpenCS. The shining example of this data category are without doubt record filters (described in the later section of the manual you are reading currently). As a mod author you probably don't need or want to distribute project files among players, but for sure you will want to aid all your colleagues working together with you on, well project. As you would imagine, project file makes sense only in combination with actual content files. In fact, each time you start to work on new content file and project file was not found it will be created.\\ +Project files extension is, to not surprise ``.project''. The whole name of the project file is the whole name of the content file with appended extensions. For instance awesomeswords.omwaddon file is associated with awesomeswords.omwaddon.project file. + +%TODO where are they stored. + \paragraph{Dependencies} Since addon is supposed to change the game it is logical that it also depends on the said game. It simply can't work otherwise. Just think about it: your modification is changing prize of the iron sword. But what if there is no iron sword in game? That's right: we get nonsense. What you want to do is to tie your addon to the files you are changing. Those can be either game files (expansion island for a game) or other addon files (house on the said island). It is a a good idea to be dependent only on files that are really changed in your addon obviously, but sadly there is no other way to achieve this than knowing what you want to do.\\ From 3f1eb42d9128bbbd2f5ca62b20e2de1d120e63a4 Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Wed, 20 Nov 2013 20:54:35 +0100 Subject: [PATCH 331/434] I hate this chapter. --- manual/opencs/files_and_directories.tex | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/manual/opencs/files_and_directories.tex b/manual/opencs/files_and_directories.tex index 307a5565f8..9ffa138289 100644 --- a/manual/opencs/files_and_directories.tex +++ b/manual/opencs/files_and_directories.tex @@ -9,22 +9,27 @@ Needless to say, this chapter describes paths and files that are used/created/ed \subsection{Basics} \paragraph{Directories} -Open{CS} user should consider two directory paths in his system. So called ``User Configuration Path'' and ``Data Path''. ``User Configuration Path'' stores basically all Open{MW} specific files that are supposed to be touched by the user, while ``Data Path''%This is wrong, pay no mind to it. +Open{MW} and Open{CS} uses multiple directories on file systems, however from end user perspective only two matters. That's it: user path and game path.\\ + +User path is the per user path that holds both configuration files and the content/project files (described later). Game path on the other hand, is the location of your game (most likely Morrowind\texttrademark). + +%TODO give correct path \paragraph{Content files} Bethesda Morrowind engine is using two types of files: esm (master) and esp (plugin). The distinction between those is not clear, and often confusing, mostly because large esp files could hurt stability. Open{MW} supports both esm and esp files, but in order to make use of new features of OpenMW one should consider using new file types designed with our engine in mind: game files and addon files together called ``content files``.\\ -Game and Addon files are concept descended from old esm/esp system, but much more flexible and cleaner. Finally, We can describe the difference between those two file types use case with pure statements. If you want to make new game using Open{MW} as engine (so called ``total conversion'') you should create a game file. If you want to create a addon for existing game file -- simply create addon file. The addon size is not a factor here. The only distinction you should consider is if your project is about changing other game, or creating a new one. Simple as that.\\ +Game and Addon files are concept descended from old esm/esp system, but much more flexible and cleaner. We can describe the difference between those two file types use case with clean statements. If you want to make new game using Open{MW} as engine (so called ``total conversion'') you should create a game file. If you want to create a addon for existing game file -- simply create addon file. The addon size is not a factor here. The only distinction you should consider is if your project is about changing other game, or creating a new one. Simple as that.\\ %TODO describe some characteristics, like file extensions. What about project files? -The actual creating of new files is described in the next chapter. Here We are gonna focus only on details that you need to know in order to create your first Open{CS} file while full understanding your needs. +The actual creating of new files is described in the next chapter. Here We are gonna focus only on details that you need to know in order to create your first Open{CS} file while full understanding your needs. For now let's jut remember that content files are created inside the user directory, in the the \textbf{data} subfolder. Open{CS} scans for content files not only data subfolder but also the game path, most likely directory where you installed or unpacked Morrowind\texttrademark. However it is probably better to not pollute game path, and use only data path for Open{MW} content files, both when creating and only playing with others content files.\\ \paragraph{Project files} Project files act as containers for data not used by the Openm{MW} game engine itself, but still useful for OpenCS. The shining example of this data category are without doubt record filters (described in the later section of the manual you are reading currently). As a mod author you probably don't need or want to distribute project files among players, but for sure you will want to aid all your colleagues working together with you on, well project. As you would imagine, project file makes sense only in combination with actual content files. In fact, each time you start to work on new content file and project file was not found it will be created.\\ -Project files extension is, to not surprise ``.project''. The whole name of the project file is the whole name of the content file with appended extensions. For instance awesomeswords.omwaddon file is associated with awesomeswords.omwaddon.project file. +Project files extension is, to not surprise ``.project''. The whole name of the project file is the whole name of the content file with appended extensions. For instance awesomeswords.omwaddon file is associated with awesomeswords.omwaddon.project file.\\ %TODO where are they stored. +Project files are stored inside the user directory, in the \textbf{projects} subfolder. This is the path location for both freshly created project files, and the place where Open{CS} look for already existing files. \paragraph{Dependencies} Since addon is supposed to change the game it is logical that it also depends on the said game. It simply can't work otherwise. Just think about it: your modification is changing prize of the iron sword. But what if there is no iron sword in game? That's right: we get nonsense. What you want to do is to tie your addon to the files you are changing. Those can be either game files (expansion island for a game) or other addon files (house on the said island). It is a a good idea to be dependent only on files that are really changed in your addon obviously, but sadly there is no other way to achieve this than knowing what you want to do.\\ From 4ed4c1e3199e437871dbc6683be7b78852b61d6c Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 21 Nov 2013 00:27:22 +0100 Subject: [PATCH 332/434] Add Vampirism and Sun Damage effects. Some fixes. --- apps/openmw/mwmechanics/actors.cpp | 40 ++++++++++------ apps/openmw/mwmechanics/actors.hpp | 2 - apps/openmw/mwmechanics/spells.cpp | 2 +- apps/openmw/mwrender/characterpreview.cpp | 12 +++-- apps/openmw/mwrender/npcanimation.cpp | 57 ++++++++++++++++++++--- 5 files changed, 84 insertions(+), 29 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index ec22c8d7f5..b9a56e30cb 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -211,7 +211,7 @@ namespace MWMechanics float currentDiff = creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::RestoreHealth+i)).mMagnitude - creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::DamageHealth+i)).mMagnitude - - creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::AbsorbHealth)).mMagnitude; + - creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::AbsorbHealth+i)).mMagnitude; stat.setCurrent(stat.getCurrent() + currentDiff * duration); creatureStats.setDynamic(i, stat); @@ -219,16 +219,34 @@ namespace MWMechanics // Apply damage ticks int damageEffects[] = { - ESM::MagicEffect::FireDamage, ESM::MagicEffect::ShockDamage, ESM::MagicEffect::FrostDamage, ESM::MagicEffect::Poison + ESM::MagicEffect::FireDamage, ESM::MagicEffect::ShockDamage, ESM::MagicEffect::FrostDamage, ESM::MagicEffect::Poison, + ESM::MagicEffect::SunDamage }; + DynamicStat health = creatureStats.getHealth(); for (unsigned int i=0; i health = creatureStats.getHealth(); - health.setCurrent(health.getCurrent() - magnitude * duration); - creatureStats.setHealth(health); + + if (damageEffects[i] == ESM::MagicEffect::SunDamage) + { + // isInCell shouldn't be needed, but updateActor called during game start + if (!ptr.isInCell() || !ptr.getCell()->isExterior()) + continue; + float time = MWBase::Environment::get().getWorld()->getTimeStamp().getHour(); + float timeDiff = std::min(7.f, std::max(0.f, std::abs(time - 13))); + float damageScale = 1.f - timeDiff / 7.f; + // When cloudy, the sun damage effect is halved + int weather = MWBase::Environment::get().getWorld()->getCurrentWeather(); + if (weather > 1) + damageScale *= 0.5; + health.setCurrent(health.getCurrent() - magnitude * duration * damageScale); + } + else + health.setCurrent(health.getCurrent() - magnitude * duration); + } + creatureStats.setHealth(health); } void Actors::calculateNpcStatModifiers (const MWWorld::Ptr& ptr) @@ -324,7 +342,7 @@ namespace MWMechanics } } - Actors::Actors() : mDuration (0) {} + Actors::Actors() {} void Actors::addActor (const MWWorld::Ptr& ptr) { @@ -377,14 +395,8 @@ namespace MWMechanics void Actors::update (float duration, bool paused) { - mDuration += duration; - - //if (mDuration>=0.25) if (!paused) { - float totalDuration = mDuration; - mDuration = 0; - for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();iter++) { const MWWorld::Class &cls = MWWorld::Class::get(iter->first); @@ -396,9 +408,9 @@ namespace MWMechanics if(iter->second->isDead()) iter->second->resurrect(); - updateActor(iter->first, totalDuration); + updateActor(iter->first, duration); if(iter->first.getTypeName() == typeid(ESM::NPC).name()) - updateNpc(iter->first, totalDuration, paused); + updateNpc(iter->first, duration, paused); if(!stats.isDead()) continue; diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 01a96f1993..b733c966be 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -30,8 +30,6 @@ namespace MWMechanics std::map mDeathCount; - float mDuration; - void updateNpc(const MWWorld::Ptr &ptr, float duration, bool paused); diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index 5b18e2a3c5..6e7ac6f315 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -66,7 +66,7 @@ namespace MWMechanics int i=0; for (std::vector::const_iterator it = spell->mEffects.mList.begin(); it != spell->mEffects.mList.end(); ++it) { - effects.add (*it, iter->second[i]); + effects.add (*it, it->mMagnMin + (it->mMagnMax - it->mMagnMin) * iter->second[i]); ++i; } } diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index b9818efbba..2cae306110 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -72,7 +72,6 @@ namespace MWRender mAnimation = new NpcAnimation(mCharacter, mNode, 0, true, (renderHeadOnly() ? NpcAnimation::VM_HeadOnly : NpcAnimation::VM_Normal)); - mAnimation->updateParts(); Ogre::Vector3 scale = mNode->getScale(); mCamera->setPosition(mPosition * scale); @@ -114,7 +113,6 @@ namespace MWRender delete mAnimation; mAnimation = new NpcAnimation(mCharacter, mNode, 0, true, (renderHeadOnly() ? NpcAnimation::VM_HeadOnly : NpcAnimation::VM_Normal)); - mAnimation->updateParts(); float scale=1.f; MWWorld::Class::get(mCharacter).adjustScale(mCharacter, scale); @@ -142,6 +140,9 @@ namespace MWRender void InventoryPreview::update(int sizeX, int sizeY) { + // TODO: can we avoid this. Vampire state needs to be updated. + mAnimation->rebuild(); + MWWorld::InventoryStore &inv = MWWorld::Class::get(mCharacter).getInventoryStore(mCharacter); MWWorld::ContainerStoreIterator iter = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); std::string groupname; @@ -176,11 +177,12 @@ namespace MWRender groupname = "inventoryhandtohand"; } - if(groupname != mCurrentAnimGroup) - { + // TODO see above + //if(groupname != mCurrentAnimGroup) + //{ mCurrentAnimGroup = groupname; mAnimation->play(mCurrentAnimGroup, 1, Animation::Group_All, false, 1.0f, "start", "stop", 0.0f, 0); - } + //} MWWorld::ContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); if(torch != inv.end() && torch->getTypeName() == typeid(ESM::Light).name()) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index c714289416..093a40c8e5 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -19,6 +19,42 @@ #include "renderconst.hpp" #include "camera.hpp" +namespace +{ + +std::string getVampireHead(const std::string& race, bool female) +{ + static std::map , const ESM::BodyPart* > sVampireMapping; + + std::pair thisCombination = std::make_pair(race, int(female)); + + if (sVampireMapping.find(thisCombination) == sVampireMapping.end()) + { + const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::Store &partStore = store.get(); + for(MWWorld::Store::iterator it = partStore.begin(); it != partStore.end(); ++it) + { + const ESM::BodyPart& bodypart = *it; + if (!bodypart.mData.mVampire) + continue; + if (bodypart.mData.mType != ESM::BodyPart::MT_Skin) + continue; + if (bodypart.mData.mPart != ESM::BodyPart::MP_Head) + continue; + if (female != (bodypart.mData.mFlags & ESM::BodyPart::BPF_Female)) + continue; + if (!Misc::StringUtils::ciEqual(bodypart.mRace, race)) + continue; + sVampireMapping[thisCombination] = &*it; + } + } + + assert(sVampireMapping[thisCombination]); + return "meshes\\" + sVampireMapping[thisCombination]->mModel; +} + +} + namespace MWRender { @@ -110,18 +146,23 @@ void NpcAnimation::updateNpcBase() const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Race *race = store.get().find(mNpc->mRace); - bool isWerewolf = MWWorld::Class::get(mPtr).getNpcStats(mPtr).isWerewolf(); + bool isWerewolf = mPtr.getClass().getNpcStats(mPtr).isWerewolf(); + bool vampire = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Vampirism).mMagnitude; - if(!isWerewolf) - { - mHeadModel = "meshes\\" + store.get().find(mNpc->mHead)->mModel; - mHairModel = "meshes\\" + store.get().find(mNpc->mHair)->mModel; - } - else + if (isWerewolf) { mHeadModel = "meshes\\" + store.get().find("WerewolfHead")->mModel; mHairModel = "meshes\\" + store.get().find("WerewolfHair")->mModel; } + else + { + if (vampire) + mHeadModel = getVampireHead(mNpc->mRace, mNpc->mFlags & ESM::NPC::Female); + else + mHeadModel = "meshes\\" + store.get().find(mNpc->mHead)->mModel; + + mHairModel = "meshes\\" + store.get().find(mNpc->mHair)->mModel; + } bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0; std::string smodel = (mViewMode != VM_FirstPerson) ? @@ -270,6 +311,8 @@ void NpcAnimation::updateParts() // Remember body parts so we only have to search through the store once for each race/gender/viewmode combination static std::map< std::pair,std::vector > sRaceMapping; + static std::map , std::vector > sVampireMapping; + static const int Flag_Female = 1<<0; static const int Flag_FirstPerson = 1<<1; From bf153e1c8e47e0b4347d63c8a22a2bb484ccebaa Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 21 Nov 2013 00:41:52 +0100 Subject: [PATCH 333/434] Fix bug applying instant effects --- apps/openmw/mwmechanics/spellcasting.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 859d8d6991..9f7073d2af 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -157,7 +157,7 @@ namespace MWMechanics } } else - applyInstantEffect(mTarget, effectIt->mEffectID, magnitude); + applyInstantEffect(target, effectIt->mEffectID, magnitude); if (target.getClass().isActor() || magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) { From 1875dea243b5fbc5bc365cec9c31f4e2f6cbc0cb Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Thu, 21 Nov 2013 13:47:01 +0100 Subject: [PATCH 334/434] Making progress on corrections. --- manual/opencs/files_and_directories.tex | 28 ++++++++++++++----------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/manual/opencs/files_and_directories.tex b/manual/opencs/files_and_directories.tex index 9ffa138289..0fd5932d50 100644 --- a/manual/opencs/files_and_directories.tex +++ b/manual/opencs/files_and_directories.tex @@ -1,35 +1,39 @@ \section{Files and Directories} \subsection{Introduction} -As you imagine, there is no other way to store and distribute computer data without files. You surely know that each file in your file system is identified by the unique path. This is basic knowledge, and applies to both Open{MW} and Open{CS} alike.\\ - -Needless to say, this chapter describes paths and files that are used/created/edited with OpenCS editor.\\ +This section of the manual covers usage of files and directories by the OpenCS. Files and directories are file system concepts, and you are probably already familiar with it. We won't try to explain this concepts, we will just focus on Open{CS}. \subsection{Used terms} %TODO \subsection{Basics} \paragraph{Directories} -Open{MW} and Open{CS} uses multiple directories on file systems, however from end user perspective only two matters. That's it: user path and game path.\\ +Open{MW} and Open{CS} uses multiple directories on file systems. First of, there is a \textbf{user directory} that holds configuration files and few different folders. The location of the user directory is hard coded for each supported operating system.\\ -User path is the per user path that holds both configuration files and the content/project files (described later). Game path on the other hand, is the location of your game (most likely Morrowind\texttrademark). +%TODO list paths. -%TODO give correct path +In addition to this single hard coded directory, both Open{MW} and Open{CS} need a place to seek for actual data files of the game: textures, models, sounds and files that store records of objects in game; dialogues and so one -- so called content files. We support multiple such paths (We call it \textbf{data paths}) as specified in the configuration. Usually one data path points to the directory where original Morrowind\texttrademark is either installed or unpacked. You are free to specify as many data paths as you would like, however, there is one special data path that, as described later, is used to store newly created content files. \paragraph{Content files} -Bethesda Morrowind engine is using two types of files: esm (master) and esp (plugin). The distinction between those is not clear, and often confusing, mostly because large esp files could hurt stability. Open{MW} supports both esm and esp files, but in order to make use of new features of OpenMW one should consider using new file types designed with our engine in mind: game files and addon files together called ``content files``.\\ +Bethesda Morrowind\texttrademark engine is using two types of files: esm (master) and esp (plugin). The distinction between those is not clear, and often confusing. You would expect the esm (master) file is used to specify one master, that is modified by the esps plugins, and indeed: this is the basic idea. However, original expansions also were made as esm files, even though they essentially could be described as a really large plugins, and therefore rather use esp files. There were technical reasons behind this decision -- somewhat valid in the case of original engine, but clearly it's better to create a system that can be used is more sensible way. Open{MW} achieves this with our own content file types.\\ +We support both esm and esp files, but in order to make use of new features of OpenMW one should consider using new file types designed with our engine in mind: game files and addon files together called ``content files``.\\ -Game and Addon files are concept descended from old esm/esp system, but much more flexible and cleaner. We can describe the difference between those two file types use case with clean statements. If you want to make new game using Open{MW} as engine (so called ``total conversion'') you should create a game file. If you want to create a addon for existing game file -- simply create addon file. The addon size is not a factor here. The only distinction you should consider is if your project is about changing other game, or creating a new one. Simple as that.\\ +The actual creating of new files is described in the next chapter. Here We are gonna focus only on details that you need to know in order to create your first Open{CS} file while full understanding your needs. For now let's jut remember that content files are created inside the user directory, in the the \textbf{data} subfolder (that is the one special data path mentioned earlier).\\ +\subparagraph{Open{MW} content files} +Game and Addon files are concept somewhat similar to the old esm/esp, only in the way it should be from the very beginning. Nothing easier to describe. If you want to make new game using Open{MW} as engine (so called ``total conversion'') you should create a game file. If you want to create a addon for existing game file -- simply create addon file. Nothing else matters: The only distinction you should consider is if your project is about changing other game, or creating a new one. Simple as that.\\ -%TODO describe some characteristics, like file extensions. What about project files? +Other simple thing about content files are extensions. We are using .omwaddon for addon files and .omwgame for game files.\\ -The actual creating of new files is described in the next chapter. Here We are gonna focus only on details that you need to know in order to create your first Open{CS} file while full understanding your needs. For now let's jut remember that content files are created inside the user directory, in the the \textbf{data} subfolder. Open{CS} scans for content files not only data subfolder but also the game path, most likely directory where you installed or unpacked Morrowind\texttrademark. However it is probably better to not pollute game path, and use only data path for Open{MW} content files, both when creating and only playing with others content files.\\ +%TODO describe what content files contains. and what not. + +\subparagraph{Morrowind content files} +Using our content files is recommended solution for projects that are intended to used with Open{MW} engine. However some players wish to use original Morrowind engine, even with it large flaws and lacking features\footnote{If this is actually wrong, We are very successful project. Yay!}. Also, more than ten years \paragraph{Project files} -Project files act as containers for data not used by the Openm{MW} game engine itself, but still useful for OpenCS. The shining example of this data category are without doubt record filters (described in the later section of the manual you are reading currently). As a mod author you probably don't need or want to distribute project files among players, but for sure you will want to aid all your colleagues working together with you on, well project. As you would imagine, project file makes sense only in combination with actual content files. In fact, each time you start to work on new content file and project file was not found it will be created.\\ +Project files act as containers for data not used by the Openm{MW} game engine itself, but still useful for OpenCS. The shining example of this data category are without doubt record filters (described in the later section of the manual you are reading currently). As a mod author you probably don't need and/or want to distribute project files among players, but for sure you will want to aid all your colleagues working together with you on, well project. As you would imagine, project file makes sense only in combination with actual content files. In fact, each time you start to work on new content file and project file was not found, it will be created.\\ Project files extension is, to not surprise ``.project''. The whole name of the project file is the whole name of the content file with appended extensions. For instance awesomeswords.omwaddon file is associated with awesomeswords.omwaddon.project file.\\ %TODO where are they stored. -Project files are stored inside the user directory, in the \textbf{projects} subfolder. This is the path location for both freshly created project files, and the place where Open{CS} look for already existing files. +Project files are stored inside the user directory, in the \textbf{projects} subfolder. This is the path location for both freshly created project files, and the place where Open{CS} look for already existing files.\\ \paragraph{Dependencies} Since addon is supposed to change the game it is logical that it also depends on the said game. It simply can't work otherwise. Just think about it: your modification is changing prize of the iron sword. But what if there is no iron sword in game? That's right: we get nonsense. What you want to do is to tie your addon to the files you are changing. Those can be either game files (expansion island for a game) or other addon files (house on the said island). It is a a good idea to be dependent only on files that are really changed in your addon obviously, but sadly there is no other way to achieve this than knowing what you want to do.\\ From b52b6a8e7e426304f01812bff4d3d08fbaaaf704 Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Thu, 21 Nov 2013 13:50:23 +0100 Subject: [PATCH 335/434] Small correction, added tiny comment. --- manual/opencs/files_and_directories.tex | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/manual/opencs/files_and_directories.tex b/manual/opencs/files_and_directories.tex index 0fd5932d50..85d6ccec9b 100644 --- a/manual/opencs/files_and_directories.tex +++ b/manual/opencs/files_and_directories.tex @@ -26,10 +26,11 @@ Other simple thing about content files are extensions. We are using .omwaddon fo %TODO describe what content files contains. and what not. \subparagraph{Morrowind content files} -Using our content files is recommended solution for projects that are intended to used with Open{MW} engine. However some players wish to use original Morrowind engine, even with it large flaws and lacking features\footnote{If this is actually wrong, We are very successful project. Yay!}. Also, more than ten years +Using our content files is recommended solution for projects that are intended to used with Open{MW} engine. However some players wish to use original Morrowind engine, even with it large flaws and lacking features\footnote{If this is actually wrong, We are very successful project. Yay!}. Also, more than ten years %not finished \paragraph{Project files} -Project files act as containers for data not used by the Openm{MW} game engine itself, but still useful for OpenCS. The shining example of this data category are without doubt record filters (described in the later section of the manual you are reading currently). As a mod author you probably don't need and/or want to distribute project files among players, but for sure you will want to aid all your colleagues working together with you on, well project. As you would imagine, project file makes sense only in combination with actual content files. In fact, each time you start to work on new content file and project file was not found, it will be created.\\ +Project files act as containers for data not used by the Openm{MW} game engine itself, but still useful for OpenCS. The shining example of this data category are without doubt record filters (described in the later section of the manual you are reading currently). As a mod author you probably don't need and/or want to distribute project files at all, they are meant to be used only by you.\\ +As you would imagine, project file makes sense only in combination with actual content files. In fact, each time you start to work on new content file and project file was not found, it will be created.\\ Project files extension is, to not surprise ``.project''. The whole name of the project file is the whole name of the content file with appended extensions. For instance awesomeswords.omwaddon file is associated with awesomeswords.omwaddon.project file.\\ %TODO where are they stored. From 800a2845b06a3e96632834d00adf2ca058a17a03 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 21 Nov 2013 03:39:55 +0100 Subject: [PATCH 336/434] Add Bound & Summon effects (will need some adjustments later) --- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwmechanics/actors.cpp | 133 ++++++++++++++++++++++ apps/openmw/mwmechanics/creaturestats.hpp | 5 + apps/openmw/mwmechanics/enchanting.cpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 4 +- apps/openmw/mwworld/worldimp.hpp | 2 +- 6 files changed, 143 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 29381eb42e..8b49463080 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -241,7 +241,7 @@ namespace MWBase virtual void localRotateObject (const MWWorld::Ptr& ptr, float x, float y, float z) = 0; - virtual void safePlaceObject(const MWWorld::Ptr& ptr,MWWorld::CellStore &Cell,ESM::Position pos) = 0; + virtual MWWorld::Ptr safePlaceObject(const MWWorld::Ptr& ptr,MWWorld::CellStore &Cell,ESM::Position pos) = 0; ///< place an object in a "safe" location (ie not in the void, etc). virtual void indexToPosition (int cellX, int cellY, float &x, float &y, bool centre = false) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index b9a56e30cb..e06c96f7f2 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -12,6 +12,8 @@ #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/player.hpp" +#include "../mwworld/manualref.hpp" +#include "../mwworld/actionequip.hpp" #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" @@ -27,6 +29,25 @@ #include "aicombat.hpp" +namespace +{ + +void adjustBoundItem (const std::string& item, bool bound, const MWWorld::Ptr& ptr) +{ + if (bound) + { + MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), item, 1); + MWWorld::ActionEquip action(*ptr.getClass().getContainerStore(ptr).add(ref.getPtr(), ptr)); + action.execute(ptr); + } + else + { + ptr.getClass().getContainerStore(ptr).remove(item, 1, ptr); + } +} + +} + namespace MWMechanics { void Actors::updateActor (const MWWorld::Ptr& ptr, float duration) @@ -247,6 +268,118 @@ namespace MWMechanics } creatureStats.setHealth(health); + + // TODO: dirty flag for magic effects to avoid some unnecessary work below? + + // Update bound effects + static std::map boundItemsMap; + if (boundItemsMap.empty()) + { + boundItemsMap[ESM::MagicEffect::BoundBattleAxe] = "battle_axe"; + boundItemsMap[ESM::MagicEffect::BoundBoots] = "boots"; + boundItemsMap[ESM::MagicEffect::BoundCuirass] = "cuirass"; + boundItemsMap[ESM::MagicEffect::BoundDagger] = "dagger"; + boundItemsMap[ESM::MagicEffect::BoundGloves] = "gauntlet"; // Note: needs both _left and _right variants, see below + boundItemsMap[ESM::MagicEffect::BoundHelm] = "helm"; + boundItemsMap[ESM::MagicEffect::BoundLongbow] = "longbow"; + boundItemsMap[ESM::MagicEffect::BoundLongsword] = "longsword"; + boundItemsMap[ESM::MagicEffect::BoundMace] = "mace"; + boundItemsMap[ESM::MagicEffect::BoundShield] = "shield"; + boundItemsMap[ESM::MagicEffect::BoundSpear] = "spear"; + } + + for (std::map::iterator it = boundItemsMap.begin(); it != boundItemsMap.end(); ++it) + { + bool found = creatureStats.mBoundItems.find(it->first) != creatureStats.mBoundItems.end(); + int magnitude = creatureStats.getMagicEffects().get(EffectKey(it->first)).mMagnitude; + if (found != (magnitude > 0)) + { + std::string item = "bound_" + it->second; + if (it->first == ESM::MagicEffect::BoundGloves) + { + adjustBoundItem(item + "_left", magnitude > 0, ptr); + adjustBoundItem(item + "_right", magnitude > 0, ptr); + } + else + adjustBoundItem(item, magnitude > 0, ptr); + + if (magnitude > 0) + creatureStats.mBoundItems.insert(it->first); + else + creatureStats.mBoundItems.erase(it->first); + } + } + + // Update summon effects + static std::map summonMap; + if (summonMap.empty()) + { + summonMap[ESM::MagicEffect::SummonAncestralGhost] = "ancestor_ghost_summon"; + summonMap[ESM::MagicEffect::SummonBear] = "BM_bear_black_summon"; + summonMap[ESM::MagicEffect::SummonBonelord] = "bonelord_summon"; + summonMap[ESM::MagicEffect::SummonBonewalker] = "bonewalker_summon"; + summonMap[ESM::MagicEffect::SummonBonewolf] = "BM_wolf_bone_summon"; + summonMap[ESM::MagicEffect::SummonCenturionSphere] = "centurion_sphere_summon"; + summonMap[ESM::MagicEffect::SummonClannfear] = "clannfear_summon"; + summonMap[ESM::MagicEffect::SummonDaedroth] = "daedroth_summon"; + summonMap[ESM::MagicEffect::SummonDremora] = "dremora_summon"; + summonMap[ESM::MagicEffect::SummonFabricant] = "fabricant_summon"; + summonMap[ESM::MagicEffect::SummonFlameAtronach] = "atronach_flame_summon"; + summonMap[ESM::MagicEffect::SummonFrostAtronach] = "atronach_frost_summon"; + summonMap[ESM::MagicEffect::SummonGoldenSaint] = "golden saint_summon"; + summonMap[ESM::MagicEffect::SummonGreaterBonewalker] = "bonewalker_greater_summ"; + summonMap[ESM::MagicEffect::SummonHunger] = "hunger_summon"; + summonMap[ESM::MagicEffect::SummonScamp] = "scamp_summon"; + summonMap[ESM::MagicEffect::SummonSkeletalMinion] = "skeleton_summon"; + summonMap[ESM::MagicEffect::SummonStormAtronach] = "atronach_storm_summon"; + summonMap[ESM::MagicEffect::SummonWingedTwilight] = "winged twilight_summon"; + summonMap[ESM::MagicEffect::SummonWolf] = "BM_wolf_grey_summon"; + } + + for (std::map::iterator it = summonMap.begin(); it != summonMap.end(); ++it) + { + bool found = creatureStats.mSummonedCreatures.find(it->first) != creatureStats.mSummonedCreatures.end(); + int magnitude = creatureStats.getMagicEffects().get(EffectKey(it->first)).mMagnitude; + if (found != (magnitude > 0)) + { + if (magnitude > 0) + { + ESM::Position ipos = ptr.getRefData().getPosition(); + Ogre::Vector3 pos(ipos.pos[0],ipos.pos[1],ipos.pos[2]); + Ogre::Quaternion rot(Ogre::Radian(-ipos.rot[2]), Ogre::Vector3::UNIT_Z); + const float distance = 50; + pos = pos + distance*rot.yAxis(); + ipos.pos[0] = pos.x; + ipos.pos[1] = pos.y; + ipos.pos[2] = pos.z; + ipos.rot[0] = 0; + ipos.rot[1] = 0; + ipos.rot[2] = 0; + + MWWorld::CellStore* store = ptr.getCell(); + MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), it->second, 1); + ref.getPtr().getCellRef().mPos = ipos; + + // TODO: Add AI to follow player and fight for him + + creatureStats.mSummonedCreatures.insert(std::make_pair(it->first, + MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),*store,ipos).getRefData().getHandle())); + + } + else + { + std::string handle = creatureStats.mSummonedCreatures[it->first]; + // TODO: Show death animation before deleting? We shouldn't allow looting the corpse while the animation + // plays though, which is a rather lame exploit in vanilla. + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaHandle(handle); + if (!ptr.isEmpty()) + { + MWBase::Environment::get().getWorld()->deleteObject(ptr); + creatureStats.mSummonedCreatures.erase(it->first); + } + } + } + } } void Actors::calculateNpcStatModifiers (const MWWorld::Ptr& ptr) diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index 126b0685f5..f28f50fc67 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -171,6 +171,11 @@ namespace MWMechanics void setLastHitObject(const std::string &objectid); const std::string &getLastHitObject() const; + + // Note, this is just a cache to avoid checking the whole container store every frame TODO: Put it somewhere else? + std::set mBoundItems; + // Same as above + std::map mSummonedCreatures; }; } diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index ba53a1a725..fda4d726eb 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -92,8 +92,8 @@ namespace MWMechanics MWWorld::Class::get(newItemPtr).applyEnchantment(newItemPtr, enchantmentPtr->mId, getGemCharge(), mNewItemName); // Add the new item to player inventory and remove the old one - store.add(newItemPtr, player); store.remove(mOldItemPtr, 1, player); + store.add(newItemPtr, player); if(!mSelfEnchanting) payForEnchantment(); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 33cc03f9f3..19ed2079ed 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1084,9 +1084,9 @@ namespace MWWorld adjust); } - void World::safePlaceObject(const MWWorld::Ptr& ptr,MWWorld::CellStore &Cell,ESM::Position pos) + MWWorld::Ptr World::safePlaceObject(const MWWorld::Ptr& ptr,MWWorld::CellStore &Cell,ESM::Position pos) { - copyObjectToCell(ptr,Cell,pos); + return copyObjectToCell(ptr,Cell,pos); } void World::indexToPosition (int cellX, int cellY, float &x, float &y, bool centre) const diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 7ccea25029..1022a74fee 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -302,7 +302,7 @@ namespace MWWorld virtual void localRotateObject (const Ptr& ptr, float x, float y, float z); - virtual void safePlaceObject(const MWWorld::Ptr& ptr,MWWorld::CellStore &Cell,ESM::Position pos); + virtual MWWorld::Ptr safePlaceObject(const MWWorld::Ptr& ptr,MWWorld::CellStore &Cell,ESM::Position pos); ///< place an object in a "safe" location (ie not in the void, etc). Makes a copy of the Ptr. virtual void indexToPosition (int cellX, int cellY, float &x, float &y, bool centre = false) From 6641fd463512a7b8b09cb7ef196d1e82e990fd06 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 21 Nov 2013 03:51:02 +0100 Subject: [PATCH 337/434] Fix bug when enchanting: only the mBase pointer was updated, not the CellRef mRefID, which is used for container stacking. The new (enchanted) item was stacking with the old item when it was added, so the enchantment completely disappears. --- apps/openmw/mwclass/armor.cpp | 1 + apps/openmw/mwclass/book.cpp | 1 + apps/openmw/mwclass/clothing.cpp | 1 + apps/openmw/mwclass/weapon.cpp | 19 ++++++++++--------- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index c8e09d4333..f916c2fb73 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -281,6 +281,7 @@ namespace MWClass newItem.mEnchant=enchId; const ESM::Armor *record = MWBase::Environment::get().getWorld()->createRecord (newItem); ref->mBase = record; + ref->mRef.mRefID = record->mId; } std::pair Armor::canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index a692b30d8f..b22cbc31fc 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -171,6 +171,7 @@ namespace MWClass newItem.mEnchant=enchId; const ESM::Book *record = MWBase::Environment::get().getWorld()->createRecord (newItem); ref->mBase = record; + ref->mRef.mRefID = record->mId; } boost::shared_ptr Book::use (const MWWorld::Ptr& ptr) const diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index 0a23821a92..8941f36275 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -227,6 +227,7 @@ namespace MWClass newItem.mEnchant=enchId; const ESM::Clothing *record = MWBase::Environment::get().getWorld()->createRecord (newItem); ref->mBase = record; + ref->mRef.mRefID = record->mId; } std::pair Clothing::canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index eaed597fcf..671e81dca0 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -370,16 +370,17 @@ namespace MWClass void Weapon::applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + MWWorld::LiveCellRef *ref = + ptr.get(); - ESM::Weapon newItem = *ref->mBase; - newItem.mId=""; - newItem.mName=newName; - newItem.mData.mEnchant=enchCharge; - newItem.mEnchant=enchId; - const ESM::Weapon *record = MWBase::Environment::get().getWorld()->createRecord (newItem); - ref->mBase = record; + ESM::Weapon newItem = *ref->mBase; + newItem.mId=""; + newItem.mName=newName; + newItem.mData.mEnchant=enchCharge; + newItem.mEnchant=enchId; + const ESM::Weapon *record = MWBase::Environment::get().getWorld()->createRecord (newItem); + ref->mBase = record; + ref->mRef.mRefID = record->mId; } std::pair Weapon::canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const From bab657fe2ba20e0f5a1f87be267508e1842a9d7d Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 21 Nov 2013 04:11:06 +0100 Subject: [PATCH 338/434] Add a utility function to add items to a ContainerStore by RefID --- apps/openmw/mwgui/tradewindow.cpp | 3 +-- apps/openmw/mwmechanics/actors.cpp | 10 +++++----- apps/openmw/mwmechanics/alchemy.cpp | 3 +-- apps/openmw/mwmechanics/enchanting.cpp | 5 +---- apps/openmw/mwscript/containerextensions.cpp | 10 ++++------ apps/openmw/mwscript/miscextensions.cpp | 9 ++------- apps/openmw/mwworld/containerstore.cpp | 6 ++++++ apps/openmw/mwworld/containerstore.hpp | 3 +++ 8 files changed, 23 insertions(+), 26 deletions(-) diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index 65e3917ed5..c1b1ff3b42 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -210,8 +210,7 @@ namespace MWGui if (amount > 0) { - MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), "Gold_001", amount); - playerStore.add(ref.getPtr(), player); + playerStore.add("gold_001", amount, player); } else { diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index e06c96f7f2..5aa846118b 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -32,17 +32,17 @@ namespace { -void adjustBoundItem (const std::string& item, bool bound, const MWWorld::Ptr& ptr) +void adjustBoundItem (const std::string& item, bool bound, const MWWorld::Ptr& actor) { if (bound) { - MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), item, 1); - MWWorld::ActionEquip action(*ptr.getClass().getContainerStore(ptr).add(ref.getPtr(), ptr)); - action.execute(ptr); + MWWorld::Ptr newPtr = *actor.getClass().getContainerStore(actor).add(item, 1, actor); + MWWorld::ActionEquip action(newPtr); + action.execute(actor); } else { - ptr.getClass().getContainerStore(ptr).remove(item, 1, ptr); + actor.getClass().getContainerStore(actor).remove(item, 1, actor); } } diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index 82580ce0e1..f994c28b84 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -287,8 +287,7 @@ void MWMechanics::Alchemy::addPotion (const std::string& name) record = MWBase::Environment::get().getWorld()->createRecord (newRecord); } - MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), record->mId); - MWWorld::Class::get (mAlchemist).getContainerStore (mAlchemist).add (ref.getPtr(), mAlchemist); + mAlchemist.getClass().getContainerStore (mAlchemist).add (record->mId, 1, mAlchemist); } void MWMechanics::Alchemy::increaseSkill() diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index fda4d726eb..7e11acdb0c 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -62,10 +62,7 @@ namespace MWMechanics //Exception for Azura Star, new one will be added after enchanting if(boost::iequals(mSoulGemPtr.get()->mBase->mId, "Misc_SoulGem_Azura")) - { - MWWorld::ManualRef azura (MWBase::Environment::get().getWorld()->getStore(), "Misc_SoulGem_Azura"); - store.add(azura.getPtr(), player); - } + store.add("Misc_SoulGem_Azura", 1, player); if(mSelfEnchanting) { diff --git a/apps/openmw/mwscript/containerextensions.cpp b/apps/openmw/mwscript/containerextensions.cpp index d124eca489..9636e8a628 100644 --- a/apps/openmw/mwscript/containerextensions.cpp +++ b/apps/openmw/mwscript/containerextensions.cpp @@ -17,7 +17,6 @@ #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" -#include "../mwworld/manualref.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/actionequip.hpp" @@ -53,24 +52,23 @@ namespace MWScript if (count == 0) return; - MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), item, count); + MWWorld::Ptr itemPtr = *ptr.getClass().getContainerStore (ptr).add (item, count, ptr); // Configure item's script variables - std::string script = MWWorld::Class::get(ref.getPtr()).getScript(ref.getPtr()); + std::string script = MWWorld::Class::get(itemPtr).getScript(itemPtr); if (script != "") { const ESM::Script *esmscript = MWBase::Environment::get().getWorld()->getStore().get().find (script); - ref.getPtr().getRefData().setLocals(*esmscript); + itemPtr.getRefData().setLocals(*esmscript); } - MWWorld::Class::get (ptr).getContainerStore (ptr).add (ref.getPtr(), ptr); // Spawn a messagebox (only for items added to player's inventory and if player is talking to someone) if (ptr == MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer() ) { // The two GMST entries below expand to strings informing the player of what, and how many of it has been added to their inventory std::string msgBox; - std::string itemName = MWWorld::Class::get(ref.getPtr()).getName(ref.getPtr()); + std::string itemName = itemPtr.getClass().getName(itemPtr); if (count == 1) { msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage60}"); diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 35f7a40447..8e2a8af8c4 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -19,7 +19,6 @@ #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" -#include "../mwworld/manualref.hpp" #include "../mwworld/containerstore.hpp" #include "../mwmechanics/npcstats.hpp" @@ -348,12 +347,8 @@ namespace MWScript const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); store.get().find(creature); // This line throws an exception if it can't find the creature - MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), gem, 1); - - ref.getPtr().getCellRef().mSoul = creature; - - MWWorld::Class::get (ptr).getContainerStore (ptr).add (ref.getPtr(), ptr); - + MWWorld::Ptr item = *ptr.getClass().getContainerStore(ptr).add(gem, 1, ptr); + item.getCellRef().mSoul = creature; } }; diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index be2e0b5a3d..d1d16ee01d 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -118,6 +118,12 @@ bool MWWorld::ContainerStore::stacks(const Ptr& ptr1, const Ptr& ptr2) || cls2.getItemMaxHealth(ptr2) == ptr2.getCellRef().mCharge); } +MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add(const std::string &id, int count, const Ptr &actorPtr) +{ + MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), id, count); + return add(ref.getPtr(), actorPtr); +} + MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr, const Ptr& actorPtr) { MWWorld::ContainerStoreIterator it = addImp(itemPtr); diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index c430b4bfc5..df7168dfa1 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -74,6 +74,9 @@ namespace MWWorld /// /// @return if stacking happened, return iterator to the item that was stacked against, otherwise iterator to the newly inserted item. + ContainerStoreIterator add(const std::string& id, int count, const Ptr& actorPtr); + ///< Utility to construct a ManualRef and call add(ptr, actorPtr) + int remove(const std::string& itemId, int count, const Ptr& actor); ///< Remove \a count item(s) designated by \a itemId from this container. /// From b6c22ad5d9ecae5ee3e3c3f215a8cbf053145eea Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 21 Nov 2013 04:27:53 +0100 Subject: [PATCH 339/434] Add starting gold for NPCs and creatures. Refactor gold removal in some gui windows (use containerstore method instead of a dependency on TradeWindow). Use real gold amount in trade window, not refill amount. --- apps/openmw/mwclass/creature.cpp | 2 ++ apps/openmw/mwclass/npc.cpp | 7 +++++ apps/openmw/mwgui/dialogue.cpp | 9 ++++-- apps/openmw/mwgui/merchantrepair.cpp | 5 +-- apps/openmw/mwgui/spellbuyingwindow.cpp | 4 +-- apps/openmw/mwgui/spellcreationdialog.cpp | 7 +++-- apps/openmw/mwgui/tradewindow.cpp | 38 ++++++++++------------- apps/openmw/mwgui/tradewindow.hpp | 2 +- apps/openmw/mwgui/trainingwindow.cpp | 4 +-- apps/openmw/mwgui/travelwindow.cpp | 8 +++-- 10 files changed, 48 insertions(+), 38 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 20f95ab0e9..9834807821 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -98,6 +98,8 @@ namespace MWClass data->mContainerStore.fill(ref->mBase->mInventory, getId(ptr), MWBase::Environment::get().getWorld()->getStore()); + data->mContainerStore.add("gold_001", ref->mBase->mData.mGold, ptr); + // store ptr.getRefData().setCustomData (data.release()); } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index e9182d0941..6970e8646e 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -184,8 +184,11 @@ namespace MWClass } // creature stats + int gold=0; if(ref->mBase->mNpdt52.mGold != -10) { + gold = ref->mBase->mNpdt52.mGold; + for (int i=0; i<27; ++i) data->mNpcStats.getSkill (i).setBase (ref->mBase->mNpdt52.mSkills[i]); @@ -207,6 +210,8 @@ namespace MWClass } else { + gold = ref->mBase->mNpdt12.mGold; + for (int i=0; i<3; ++i) data->mNpcStats.setDynamic (i, 10); @@ -236,6 +241,8 @@ namespace MWClass // store ptr.getRefData().setCustomData (data.release()); + getContainerStore(ptr).add("gold_001", gold, ptr); + getInventoryStore(ptr).autoEquip(ptr); } } diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index c9a7806918..71995f97fd 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -12,6 +12,8 @@ #include "../mwmechanics/npcstats.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/player.hpp" +#include "../mwworld/containerstore.hpp" #include "../mwdialogue/dialoguemanagerimp.hpp" @@ -67,23 +69,24 @@ namespace MWGui void PersuasionDialog::onPersuade(MyGUI::Widget *sender) { + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); MWBase::MechanicsManager::PersuasionType type; if (sender == mAdmireButton) type = MWBase::MechanicsManager::PT_Admire; else if (sender == mIntimidateButton) type = MWBase::MechanicsManager::PT_Intimidate; else if (sender == mTauntButton) type = MWBase::MechanicsManager::PT_Taunt; else if (sender == mBribe10Button) { - MWBase::Environment::get().getWindowManager()->getTradeWindow()->addOrRemoveGold(-10); + player.getClass().getContainerStore(player).remove("gold_001", 10, player); type = MWBase::MechanicsManager::PT_Bribe10; } else if (sender == mBribe100Button) { - MWBase::Environment::get().getWindowManager()->getTradeWindow()->addOrRemoveGold(-100); + player.getClass().getContainerStore(player).remove("gold_001", 100, player); type = MWBase::MechanicsManager::PT_Bribe100; } else /*if (sender == mBribe1000Button)*/ { - MWBase::Environment::get().getWindowManager()->getTradeWindow()->addOrRemoveGold(-1000); + player.getClass().getContainerStore(player).remove("gold_001", 1000, player); type = MWBase::MechanicsManager::PT_Bribe1000; } diff --git a/apps/openmw/mwgui/merchantrepair.cpp b/apps/openmw/mwgui/merchantrepair.cpp index 530594ddaa..4da1668209 100644 --- a/apps/openmw/mwgui/merchantrepair.cpp +++ b/apps/openmw/mwgui/merchantrepair.cpp @@ -13,7 +13,6 @@ #include "../mwworld/containerstore.hpp" #include "inventorywindow.hpp" -#include "tradewindow.hpp" namespace MWGui { @@ -119,7 +118,9 @@ void MerchantRepair::onRepairButtonClick(MyGUI::Widget *sender) MWBase::Environment::get().getSoundManager()->playSound("Repair",1,1); int price = boost::lexical_cast(sender->getUserString("Price")); - MWBase::Environment::get().getWindowManager()->getTradeWindow()->addOrRemoveGold(-price); + + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + player.getClass().getContainerStore(player).remove("gold_001", price, player); startRepair(mActor); } diff --git a/apps/openmw/mwgui/spellbuyingwindow.cpp b/apps/openmw/mwgui/spellbuyingwindow.cpp index a7fcfdd021..bbd28b2de6 100644 --- a/apps/openmw/mwgui/spellbuyingwindow.cpp +++ b/apps/openmw/mwgui/spellbuyingwindow.cpp @@ -10,11 +10,11 @@ #include "../mwworld/player.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/containerstore.hpp" #include "../mwmechanics/creaturestats.hpp" #include "inventorywindow.hpp" -#include "tradewindow.hpp" namespace MWGui { @@ -123,7 +123,7 @@ namespace MWGui MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); MWMechanics::Spells& spells = stats.getSpells(); spells.add (mSpellsWidgetMap.find(_sender)->second); - MWBase::Environment::get().getWindowManager()->getTradeWindow()->addOrRemoveGold(-price); + player.getClass().getContainerStore(player).remove("gold_001", price, player); startSpellBuying(mPtr); MWBase::Environment::get().getSoundManager()->playSound ("Item Gold Up", 1.0, 1.0); diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index e0b808b283..b9324fea17 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -8,13 +8,13 @@ #include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/player.hpp" +#include "../mwworld/containerstore.hpp" #include "../mwmechanics/spellcasting.hpp" #include "tooltips.hpp" #include "class.hpp" #include "inventorywindow.hpp" -#include "tradewindow.hpp" namespace { @@ -342,13 +342,14 @@ namespace MWGui mSpell.mName = mNameEdit->getCaption(); - MWBase::Environment::get().getWindowManager()->getTradeWindow()->addOrRemoveGold(-boost::lexical_cast(mPriceLabel->getCaption())); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + + player.getClass().getContainerStore(player).remove("gold_001", boost::lexical_cast(mPriceLabel->getCaption()), player); MWBase::Environment::get().getSoundManager()->playSound ("Item Gold Up", 1.0, 1.0); const ESM::Spell* spell = MWBase::Environment::get().getWorld()->createRecord(mSpell); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); MWMechanics::Spells& spells = stats.getSpells(); spells.add (spell->mId); diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index c1b1ff3b42..636b8ae9b6 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -203,18 +203,17 @@ namespace MWGui sellToNpc(item.mBase, count, true); } - void TradeWindow::addOrRemoveGold(int amount) + void TradeWindow::addOrRemoveGold(int amount, const MWWorld::Ptr& actor) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); - MWWorld::ContainerStore& playerStore = MWWorld::Class::get(player).getContainerStore(player); + MWWorld::ContainerStore& store = MWWorld::Class::get(actor).getContainerStore(actor); if (amount > 0) { - playerStore.add("gold_001", amount, player); + store.add("gold_001", amount, actor); } else { - playerStore.remove("gold_001", - amount, player); + store.remove("gold_001", - amount, actor); } } @@ -269,6 +268,8 @@ namespace MWGui return; } + MWWorld::Ptr playerPtr = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + if(mCurrentBalance > mCurrentMerchantOffer) { //if npc is a creature: reject (no haggle) @@ -291,7 +292,6 @@ namespace MWGui + MWBase::Environment::get().getDialogueManager()->getTemporaryDispositionChange()),100)); const MWMechanics::NpcStats &sellerStats = MWWorld::Class::get(mPtr).getNpcStats(mPtr); - MWWorld::Ptr playerPtr = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); const MWMechanics::NpcStats &playerStats = MWWorld::Class::get(playerPtr).getNpcStats(playerPtr); float a1 = std::min(playerStats.getSkill(ESM::Skill::Mercantile).getModified(), 100.f); @@ -331,9 +331,12 @@ namespace MWGui mTradeModel->transferItems(); playerItemModel->transferItems(); - // add or remove gold from the player. + // transfer the gold if (mCurrentBalance != 0) - addOrRemoveGold(mCurrentBalance); + { + addOrRemoveGold(mCurrentBalance, playerPtr); + addOrRemoveGold(-mCurrentBalance, mPtr); + } std::string sound = "Item Gold Up"; MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); @@ -434,22 +437,13 @@ namespace MWGui int TradeWindow::getMerchantGold() { - int merchantGold; - - if (mPtr.getTypeName() == typeid(ESM::NPC).name()) + int merchantGold = 0; + MWWorld::ContainerStore store = mPtr.getClass().getContainerStore(mPtr); + for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { - MWWorld::LiveCellRef* ref = mPtr.get(); - if (ref->mBase->mNpdt52.mGold == -10) - merchantGold = ref->mBase->mNpdt12.mGold; - else - merchantGold = ref->mBase->mNpdt52.mGold; + if (Misc::StringUtils::ciEqual(it->getCellRef().mRefID, "gold_001")) + merchantGold += it->getRefData().getCount(); } - else // ESM::Creature - { - MWWorld::LiveCellRef* ref = mPtr.get(); - merchantGold = ref->mBase->mData.mGold; - } - return merchantGold; } } diff --git a/apps/openmw/mwgui/tradewindow.hpp b/apps/openmw/mwgui/tradewindow.hpp index 4e905915a0..7c11bd5394 100644 --- a/apps/openmw/mwgui/tradewindow.hpp +++ b/apps/openmw/mwgui/tradewindow.hpp @@ -28,7 +28,7 @@ namespace MWGui void startTrade(const MWWorld::Ptr& actor); - void addOrRemoveGold(int gold); + void addOrRemoveGold(int gold, const MWWorld::Ptr& actor); void onFrame(float frameDuration); diff --git a/apps/openmw/mwgui/trainingwindow.cpp b/apps/openmw/mwgui/trainingwindow.cpp index 7ddac38f54..04eddcb173 100644 --- a/apps/openmw/mwgui/trainingwindow.cpp +++ b/apps/openmw/mwgui/trainingwindow.cpp @@ -11,11 +11,11 @@ #include "../mwworld/player.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/containerstore.hpp" #include "../mwmechanics/npcstats.hpp" #include "inventorywindow.hpp" -#include "tradewindow.hpp" #include "tooltips.hpp" namespace MWGui @@ -142,7 +142,7 @@ namespace MWGui pcStats.increaseSkill (skillId, *class_, true); // remove gold - MWBase::Environment::get().getWindowManager()->getTradeWindow()->addOrRemoveGold(-price); + player.getClass().getContainerStore(player).remove("gold_001", price, player); // go back to game mode MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Training); diff --git a/apps/openmw/mwgui/travelwindow.cpp b/apps/openmw/mwgui/travelwindow.cpp index 93ac8299d9..dd5da4522f 100644 --- a/apps/openmw/mwgui/travelwindow.cpp +++ b/apps/openmw/mwgui/travelwindow.cpp @@ -11,9 +11,9 @@ #include "../mwworld/player.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/containerstore.hpp" #include "inventorywindow.hpp" -#include "tradewindow.hpp" namespace MWGui { @@ -121,13 +121,15 @@ namespace MWGui int price; iss >> price; + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + if (MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold()getTradeWindow ()->addOrRemoveGold (-price); + + player.getClass().getContainerStore(player).remove("gold_001", price, player); MWBase::Environment::get().getWorld ()->getFader ()->fadeOut(1); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); ESM::Position pos = *_sender->getUserData(); std::string cellname = _sender->getUserString("Destination"); int x,y; From 6451b687d9939831c79bc5e42f0dd74d0cb0999a Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 21 Nov 2013 04:45:40 +0100 Subject: [PATCH 340/434] Adjust value for several item types as according to wiki --- apps/openmw/mwclass/armor.cpp | 5 ++++- apps/openmw/mwclass/lockpick.cpp | 5 ++++- apps/openmw/mwclass/probe.cpp | 5 ++++- apps/openmw/mwclass/repair.cpp | 5 ++++- apps/openmw/mwclass/weapon.cpp | 5 ++++- 5 files changed, 20 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index f916c2fb73..f3f36542a1 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -169,7 +169,10 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - return ref->mBase->mData.mValue; + if (ptr.getCellRef().mCharge == -1) + return ref->mBase->mData.mValue; + else + return ref->mBase->mData.mValue * (ptr.getCellRef().mCharge / getItemMaxHealth(ptr)); } void Armor::registerSelf() diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index aff36ba81d..73b47d6af9 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -86,7 +86,10 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - return ref->mBase->mData.mValue; + if (ptr.getCellRef().mCharge == -1) + return ref->mBase->mData.mValue; + else + return ref->mBase->mData.mValue * (ptr.getCellRef().mCharge / getItemMaxHealth(ptr)); } void Lockpick::registerSelf() diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index 5cff140a65..845c2a0d00 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -85,7 +85,10 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - return ref->mBase->mData.mValue; + if (ptr.getCellRef().mCharge == -1) + return ref->mBase->mData.mValue; + else + return ref->mBase->mData.mValue * (ptr.getCellRef().mCharge / getItemMaxHealth(ptr)); } void Probe::registerSelf() diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index 38c15ac92e..dbfa9f0f62 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -76,7 +76,10 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - return ref->mBase->mData.mValue; + if (ptr.getCellRef().mCharge == -1) + return ref->mBase->mData.mValue; + else + return ref->mBase->mData.mValue * (ptr.getCellRef().mCharge / getItemMaxHealth(ptr)); } void Repair::registerSelf() diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 671e81dca0..b1bf2b0b7f 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -154,7 +154,10 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - return ref->mBase->mData.mValue; + if (ptr.getCellRef().mCharge == -1) + return ref->mBase->mData.mValue; + else + return ref->mBase->mData.mValue * (ptr.getCellRef().mCharge / getItemMaxHealth(ptr)); } void Weapon::registerSelf() From 320ba98097d4ef6e79117419e08e0dc1d2440d9e Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 21 Nov 2013 04:53:53 +0100 Subject: [PATCH 341/434] Correct getDerivedDisposition according to wiki (check if player expelled) --- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 8acc9866a8..ff13841a2f 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -419,7 +419,7 @@ namespace MWMechanics MWWorld::LiveCellRef* player = playerPtr.get(); const MWMechanics::NpcStats &playerStats = MWWorld::Class::get(playerPtr).getNpcStats(playerPtr); - if (Misc::StringUtils::lowerCase(npc->mBase->mRace) == Misc::StringUtils::lowerCase(player->mBase->mRace)) + if (Misc::StringUtils::ciEqual(npc->mBase->mRace, player->mBase->mRace)) x += MWBase::Environment::get().getWorld()->getStore().get().find("fDispRaceMod")->getFloat(); x += MWBase::Environment::get().getWorld()->getStore().get().find("fDispPersonalityMult")->getFloat() @@ -435,7 +435,9 @@ namespace MWMechanics for(std::vector::const_iterator it = MWBase::Environment::get().getWorld()->getStore().get().find(Misc::StringUtils::lowerCase(npcFaction))->mReactions.begin(); it != MWBase::Environment::get().getWorld()->getStore().get().find(Misc::StringUtils::lowerCase(npcFaction))->mReactions.end(); ++it) { - if(Misc::StringUtils::lowerCase(it->mFaction) == Misc::StringUtils::lowerCase(npcFaction)) reaction = it->mReaction; + if(Misc::StringUtils::lowerCase(it->mFaction) == Misc::StringUtils::lowerCase(npcFaction) + && playerStats.getExpelled().find(Misc::StringUtils::lowerCase(it->mFaction)) == playerStats.getExpelled().end()) + reaction = it->mReaction; } rank = playerStats.getFactionRanks().find(Misc::StringUtils::lowerCase(npcFaction))->second; } @@ -446,7 +448,8 @@ namespace MWMechanics { if(playerStats.getFactionRanks().find(Misc::StringUtils::lowerCase(it->mFaction)) != playerStats.getFactionRanks().end() ) { - if(it->mReactionmReaction; + if(it->mReaction < reaction) + reaction = it->mReaction; } } rank = 0; From 61ab6e1739c504f2dd42cc155c2e65c0f94f1f1c Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 21 Nov 2013 05:01:55 +0100 Subject: [PATCH 342/434] Use fWortChanceValue for making ingredient/potion effects visible --- apps/openmw/mwclass/ingredient.cpp | 11 +++++++---- apps/openmw/mwclass/potion.cpp | 11 ++++++----- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index f629cc15d1..06d9d5d235 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -156,6 +156,9 @@ namespace MWClass MWMechanics::NpcStats& npcStats = MWWorld::Class::get(player).getNpcStats (player); int alchemySkill = npcStats.getSkill (ESM::Skill::Alchemy).getBase(); + static const float fWortChanceValue = + MWBase::Environment::get().getWorld()->getStore().get().find("fWortChanceValue")->getFloat(); + MWGui::Widgets::SpellEffectList list; for (int i=0; i<4; ++i) { @@ -166,10 +169,10 @@ namespace MWClass params.mAttribute = ref->mBase->mData.mAttributes[i]; params.mSkill = ref->mBase->mData.mSkills[i]; - params.mKnown = ( (i == 0 && alchemySkill >= 15) - || (i == 1 && alchemySkill >= 30) - || (i == 2 && alchemySkill >= 45) - || (i == 3 && alchemySkill >= 60)); + params.mKnown = ( (i == 0 && alchemySkill >= fWortChanceValue) + || (i == 1 && alchemySkill >= fWortChanceValue*2) + || (i == 2 && alchemySkill >= fWortChanceValue*3) + || (i == 3 && alchemySkill >= fWortChanceValue*4)); list.push_back(params); } diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 2f9e63d138..883473eb33 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -137,13 +137,14 @@ namespace MWClass MWMechanics::NpcStats& npcStats = MWWorld::Class::get(player).getNpcStats (player); int alchemySkill = npcStats.getSkill (ESM::Skill::Alchemy).getBase(); int i=0; + static const float fWortChanceValue = + MWBase::Environment::get().getWorld()->getStore().get().find("fWortChanceValue")->getFloat(); for (MWGui::Widgets::SpellEffectList::iterator it = info.effects.begin(); it != info.effects.end(); ++it) { - /// \todo this code is duplicated from mwclass/ingredient, put it in a helper function - it->mKnown = ( (i == 0 && alchemySkill >= 15) - || (i == 1 && alchemySkill >= 30) - || (i == 2 && alchemySkill >= 45) - || (i == 3 && alchemySkill >= 60)); + it->mKnown = ( (i == 0 && alchemySkill >= fWortChanceValue) + || (i == 1 && alchemySkill >= fWortChanceValue*2) + || (i == 2 && alchemySkill >= fWortChanceValue*3) + || (i == 3 && alchemySkill >= fWortChanceValue*4)); ++i; } From b490e56ba182fd3bd8951b73aab3d4a43dad325a Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 21 Nov 2013 06:27:19 +0100 Subject: [PATCH 343/434] Remove redundant setLocals (already done by ContainerStore::add) --- apps/openmw/mwscript/containerextensions.cpp | 9 --------- apps/openmw/mwworld/worldimp.cpp | 16 +++------------- 2 files changed, 3 insertions(+), 22 deletions(-) diff --git a/apps/openmw/mwscript/containerextensions.cpp b/apps/openmw/mwscript/containerextensions.cpp index 9636e8a628..53f4c23c97 100644 --- a/apps/openmw/mwscript/containerextensions.cpp +++ b/apps/openmw/mwscript/containerextensions.cpp @@ -54,15 +54,6 @@ namespace MWScript MWWorld::Ptr itemPtr = *ptr.getClass().getContainerStore (ptr).add (item, count, ptr); - // Configure item's script variables - std::string script = MWWorld::Class::get(itemPtr).getScript(itemPtr); - if (script != "") - { - const ESM::Script *esmscript = MWBase::Environment::get().getWorld()->getStore().get().find (script); - itemPtr.getRefData().setLocals(*esmscript); - } - - // Spawn a messagebox (only for items added to player's inventory and if player is talking to someone) if (ptr == MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer() ) { diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 19ed2079ed..0ad06cf265 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1967,23 +1967,13 @@ namespace MWWorld if(werewolf) { - ManualRef ref(getStore(), "WerewolfRobe"); + InventoryStore &inv = actor.getClass().getInventoryStore(actor); - // Configure item's script variables - std::string script = Class::get(ref.getPtr()).getScript(ref.getPtr()); - if(script != "") - { - const ESM::Script *esmscript = getStore().get().find(script); - ref.getPtr().getRefData().setLocals(*esmscript); - } - - // Not sure this is right - InventoryStore &inv = Class::get(actor).getInventoryStore(actor); - inv.equip(InventoryStore::Slot_Robe, inv.add(ref.getPtr(), actor), actor); + inv.equip(InventoryStore::Slot_Robe, inv.ContainerStore::add("WerewolfRobe", 1, actor), actor); } else { - Class::get(actor).getContainerStore(actor).remove("WerewolfRobe", 1, actor); + actor.getClass().getContainerStore(actor).remove("WerewolfRobe", 1, actor); } if(actor.getRefData().getHandle() == "player") From 45203c160fa42b75d70c39802c1c27bd310ec142 Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Thu, 21 Nov 2013 20:07:37 +0100 Subject: [PATCH 344/434] rearranging, correcting, small changes -- nothing interesting. --- manual/opencs/files_and_directories.tex | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/manual/opencs/files_and_directories.tex b/manual/opencs/files_and_directories.tex index 85d6ccec9b..3d418d5977 100644 --- a/manual/opencs/files_and_directories.tex +++ b/manual/opencs/files_and_directories.tex @@ -17,7 +17,6 @@ In addition to this single hard coded directory, both Open{MW} and Open{CS} need Bethesda Morrowind\texttrademark engine is using two types of files: esm (master) and esp (plugin). The distinction between those is not clear, and often confusing. You would expect the esm (master) file is used to specify one master, that is modified by the esps plugins, and indeed: this is the basic idea. However, original expansions also were made as esm files, even though they essentially could be described as a really large plugins, and therefore rather use esp files. There were technical reasons behind this decision -- somewhat valid in the case of original engine, but clearly it's better to create a system that can be used is more sensible way. Open{MW} achieves this with our own content file types.\\ We support both esm and esp files, but in order to make use of new features of OpenMW one should consider using new file types designed with our engine in mind: game files and addon files together called ``content files``.\\ -The actual creating of new files is described in the next chapter. Here We are gonna focus only on details that you need to know in order to create your first Open{CS} file while full understanding your needs. For now let's jut remember that content files are created inside the user directory, in the the \textbf{data} subfolder (that is the one special data path mentioned earlier).\\ \subparagraph{Open{MW} content files} Game and Addon files are concept somewhat similar to the old esm/esp, only in the way it should be from the very beginning. Nothing easier to describe. If you want to make new game using Open{MW} as engine (so called ``total conversion'') you should create a game file. If you want to create a addon for existing game file -- simply create addon file. Nothing else matters: The only distinction you should consider is if your project is about changing other game, or creating a new one. Simple as that.\\ @@ -26,7 +25,16 @@ Other simple thing about content files are extensions. We are using .omwaddon fo %TODO describe what content files contains. and what not. \subparagraph{Morrowind content files} -Using our content files is recommended solution for projects that are intended to used with Open{MW} engine. However some players wish to use original Morrowind engine, even with it large flaws and lacking features\footnote{If this is actually wrong, We are very successful project. Yay!}. Also, more than ten years %not finished +Using our content files is recommended solution for projects that are intended to used with Open{MW} engine. However some players wish to use original Morrowind engine, even with it large flaws and lacking features\footnote{If this is actually wrong, We are very successful project. Yay!}. Also, since 2002 thousands of esp/ems files were created, some with really outstanding content. Because of this Open{CS} simply has no other choice but support esp/esm files. However, if you decided to choose esp/esm file instead using our own content file types you are most likely aim at the original engine compatibility. This subject is covered in the very last section of this manual.\\ %not finished TODO add the said section. Most likely when more features are present. + +The actual creation of new files is described in the next chapter. Here We are gonna focus only on details that you need to know in order to create your first Open{CS} file while full understanding your needs. For now let's jut remember that content files are created inside the user directory, in the the \textbf{data} subfolder (that is the one special data directory mentioned earlier).\\ + +\subparagraph{Dependencies} +Since addon is supposed to change the game it is logical that it also depends on the said game. It simply can't work otherwise. Just think about it: your modification is changing prize of the iron sword. But what if there is no iron sword in game? That's right: we get nonsense. What you want to do is to tie your addon to the files you are changing. Those can be either game files (expansion island for a game) or other addon files (house on the said island). It is a a good idea to be dependent only on files that are really changed in your addon obviously, but sadly there is no other way to achieve this than knowing what you want to do. Again, please remember that this section of the manual does not cover creating the content files -- it is only theoretical introduction to the subject. For now just keep in mind that dependencies exist, and is up to you what to decide if your content file should depend on other content file.\\ + +Game files are not intend to have any dependencies for a very simple reasons: player is using only one game file (excluding original and dirty esp/esm system) at the time and therefore no game file can depend on other game file, and since game file makes the base for addon files -- it can't depend on addon files.\\ + +\subparagraph{Loading order} \paragraph{Project files} Project files act as containers for data not used by the Openm{MW} game engine itself, but still useful for OpenCS. The shining example of this data category are without doubt record filters (described in the later section of the manual you are reading currently). As a mod author you probably don't need and/or want to distribute project files at all, they are meant to be used only by you.\\ @@ -36,12 +44,5 @@ Project files extension is, to not surprise ``.project''. The whole name of the %TODO where are they stored. Project files are stored inside the user directory, in the \textbf{projects} subfolder. This is the path location for both freshly created project files, and the place where Open{CS} look for already existing files.\\ -\paragraph{Dependencies} -Since addon is supposed to change the game it is logical that it also depends on the said game. It simply can't work otherwise. Just think about it: your modification is changing prize of the iron sword. But what if there is no iron sword in game? That's right: we get nonsense. What you want to do is to tie your addon to the files you are changing. Those can be either game files (expansion island for a game) or other addon files (house on the said island). It is a a good idea to be dependent only on files that are really changed in your addon obviously, but sadly there is no other way to achieve this than knowing what you want to do.\\ - -Game files are not intend to have any dependencies for a very simple reasons: player is using only one game file at the time and therefore no game file can depend on other game file, and since game file makes the base for addon files -- it can't depend on addon files. - -\paragraph{Loading order} - \paragraph{Resources files} %textures, sounds, whatever \ No newline at end of file From 829512ded4ee1d2307735a8c61eb580ccfabc55e Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 22 Nov 2013 01:02:12 +0100 Subject: [PATCH 345/434] Fix container scripts not getting re-added when the player changes cells --- apps/openmw/mwworld/worldimp.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 0ad06cf265..0439958f24 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -882,6 +882,7 @@ namespace MWWorld int cellY = newCell.mCell->getGridY(); mWorldScene->changeCell(cellX, cellY, pos, false); } + addContainerScripts (ptr, &newCell); } else { From 39de0510a00b8f566701178740d38acacf4cdb1e Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 22 Nov 2013 02:12:37 +0100 Subject: [PATCH 346/434] Fix another case of container scripts not getting re-added --- apps/openmw/mwworld/worldimp.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 0439958f24..fba08db951 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -749,12 +749,16 @@ namespace MWWorld void World::changeToInteriorCell (const std::string& cellName, const ESM::Position& position) { - return mWorldScene->changeToInteriorCell(cellName, position); + removeContainerScripts(getPlayer().getPlayer()); + mWorldScene->changeToInteriorCell(cellName, position); + addContainerScripts(getPlayer().getPlayer(), getPlayer().getPlayer().getCell()); } void World::changeToExteriorCell (const ESM::Position& position) { - return mWorldScene->changeToExteriorCell(position); + removeContainerScripts(getPlayer().getPlayer()); + mWorldScene->changeToExteriorCell(position); + addContainerScripts(getPlayer().getPlayer(), getPlayer().getPlayer().getCell()); } void World::markCellAsUnchanged() From 05245c15af9e660cea93a2a02eaad9be89c420ff Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Fri, 22 Nov 2013 09:44:32 +0100 Subject: [PATCH 347/434] Nothing interesting. Just keeping sync. --- manual/opencs/files_and_directories.tex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manual/opencs/files_and_directories.tex b/manual/opencs/files_and_directories.tex index 3d418d5977..1ef53b137c 100644 --- a/manual/opencs/files_and_directories.tex +++ b/manual/opencs/files_and_directories.tex @@ -42,7 +42,7 @@ As you would imagine, project file makes sense only in combination with actual c Project files extension is, to not surprise ``.project''. The whole name of the project file is the whole name of the content file with appended extensions. For instance awesomeswords.omwaddon file is associated with awesomeswords.omwaddon.project file.\\ %TODO where are they stored. -Project files are stored inside the user directory, in the \textbf{projects} subfolder. This is the path location for both freshly created project files, and the place where Open{CS} look for already existing files.\\ +Project files are stored inside the user directory, in the \textbf{projects} subfolder. This is the path location for both freshly created project files, and a place where Open{CS} looks for already existing files.\\ \paragraph{Resources files} %textures, sounds, whatever \ No newline at end of file From c5acfbf1337cf753c5be5d32877238d6ab67a7bd Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Fri, 22 Nov 2013 14:44:39 +0100 Subject: [PATCH 348/434] Started introduction to the resources files. --- manual/opencs/files_and_directories.tex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/manual/opencs/files_and_directories.tex b/manual/opencs/files_and_directories.tex index 1ef53b137c..032a7cdce4 100644 --- a/manual/opencs/files_and_directories.tex +++ b/manual/opencs/files_and_directories.tex @@ -45,4 +45,5 @@ Project files extension is, to not surprise ``.project''. The whole name of the Project files are stored inside the user directory, in the \textbf{projects} subfolder. This is the path location for both freshly created project files, and a place where Open{CS} looks for already existing files.\\ \paragraph{Resources files} -%textures, sounds, whatever \ No newline at end of file +%textures, sounds, whatever +Unless we are talking about the fully text based game, like Zork or Rogue, you are expecting that a video game is using some media files: models with textures, pictures acting as icons, sounds and everything else. Since content files, no matter if it is esp, esm or new Open{MW} file type do not contain any of those, it's clear that they have to be deliver with a different file. It is also clear that this, let's call it ``resources file``, have to be supported by the engine. Without code handling those files, it is nothing more than a mathematical abstraction -- something, that lacks meaning for human beings\footnote{Unless we call programmers a human beings.}. Therefore this section must cover ways to add resources files to your content file, and point out what is supported. We are going to do just that. Later, you will learn how to make use of those files in your content. \ No newline at end of file From 0e254aa7c764ad90a206c208ad08726b68e25917 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 23 Nov 2013 03:25:55 +0100 Subject: [PATCH 349/434] Fix startNewGame assigning an already freed CellStore to the player Ptr supplied to WindowManager. Fixes a crash when equipping lights after starting a new game (bug 967). Side note: The inventory preview's Ptr being assigned a cell at all doesn't make sense, as that is used to determine the light setting which should be the same no matter which cell you're in. --- apps/openmw/mwrender/characterpreview.cpp | 4 ++-- apps/openmw/mwworld/worldimp.cpp | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 2cae306110..5e659ca1d7 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -35,7 +35,7 @@ namespace MWRender , mCamera(NULL) , mNode(NULL) { - + mCharacter.mCell = NULL; } void CharacterPreview::onSetup() @@ -230,7 +230,7 @@ namespace MWRender , mRef(&mBase) { mBase = *mCharacter.get()->mBase; - mCharacter = MWWorld::Ptr(&mRef, mCharacter.getCell()); + mCharacter = MWWorld::Ptr(&mRef, NULL); } void RaceSelectionPreview::update(float angle) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index fba08db951..3c49b87390 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -267,6 +267,7 @@ namespace MWWorld // Rebuild player setupPlayer(); + mPlayer->setCell(NULL); MWWorld::Ptr player = mPlayer->getPlayer(); // removes NpcStats, ContainerStore etc From a1fe07181c2c8bec56dc18800f1a7d0c8baaaae8 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 23 Nov 2013 05:55:03 +0100 Subject: [PATCH 350/434] Fix rotation offset of some lights (bug 955) --- apps/openmw/mwrender/npcanimation.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 093a40c8e5..e219bcf42f 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -540,10 +540,13 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g if(skel->hasBone("BoneOffset")) { Ogre::Bone *offset = skel->getBone("BoneOffset"); + root->translate(offset->getPosition()); - root->rotate(offset->getOrientation()); - // HACK: Why an extra -90 degree rotation? + + // It appears that the BoneOffset rotation is completely bogus, at least for light models. + //root->rotate(offset->getOrientation()); root->pitch(Ogre::Degree(-90.0f)); + root->scale(offset->getScale()); root->setInitialState(); } From d2ed77f3f28e8eadb9cf29d312572d921e91b17e Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 23 Nov 2013 20:24:52 +0100 Subject: [PATCH 351/434] Fix shields being visible during spellcasting --- apps/openmw/mwmechanics/character.cpp | 6 ++++++ apps/openmw/mwrender/animation.hpp | 1 + apps/openmw/mwrender/npcanimation.cpp | 30 +++++++++++++++++++++++++++ apps/openmw/mwrender/npcanimation.hpp | 2 ++ 4 files changed, 39 insertions(+) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index c7a6b38751..7505d34056 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -426,6 +426,11 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun { forcestateupdate = true; + // Shields shouldn't be visible during spellcasting + // There seems to be no text keys for this purpose, except maybe for "[un]equip start/stop", + // but they are also present in weapon drawing animation. + mAnimation->showShield(weaptype != WeapType_Spell); + std::string weapgroup; if(weaptype == WeapType_None) { @@ -443,6 +448,7 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun MWRender::Animation::Group_UpperBody, true, 1.0f, "equip start", "equip stop", 0.0f, 0); mUpperBodyState = UpperCharState_EquipingWeap; + if(isWerewolf) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 38e7742ef1..e28aecbc13 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -274,6 +274,7 @@ public: virtual Ogre::Vector3 runAnimation(float duration); virtual void showWeapons(bool showWeapon); + virtual void showShield(bool show) {} void enableLights(bool enable); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index e219bcf42f..50e41062a8 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -110,6 +110,7 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, int v mListenerDisabled(disableListener), mViewMode(viewMode), mShowWeapons(false), + mShowShield(true), mFirstPersonOffset(0.f, 0.f, 0.f) { mNpc = mPtr.get()->mBase; @@ -307,6 +308,7 @@ void NpcAnimation::updateParts() } showWeapons(mShowWeapons); + showShield(mShowShield); // Remember body parts so we only have to search through the store once for each race/gender/viewmode combination static std::map< std::pair,std::vector > sRaceMapping; @@ -635,6 +637,34 @@ void NpcAnimation::showWeapons(bool showWeapon) } } +void NpcAnimation::showShield(bool show) +{ + mShowShield = show; + MWWorld::InventoryStore &inv = MWWorld::Class::get(mPtr).getInventoryStore(mPtr); + MWWorld::ContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); + + if (shield != inv.end() && shield->getTypeName() == typeid(ESM::Light).name()) + { + // ... Except for lights, which are still shown during spellcasting since they + // have their own (one-handed) casting animations + show = true; + } + if(show && shield != inv.end()) + { + Ogre::Vector3 glowColor = getEnchantmentColor(*shield); + std::string mesh = MWWorld::Class::get(*shield).getModel(*shield); + addOrReplaceIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, 1, + mesh, !shield->getClass().getEnchantment(*shield).empty(), &glowColor); + + if (shield->getTypeName() == typeid(ESM::Light).name()) + addExtraLight(mInsert->getCreator(), mObjectParts[ESM::PRT_Shield], shield->get()->mBase); + } + else + { + removeIndividualPart(ESM::PRT_Shield); + } +} + void NpcAnimation::permanentEffectAdded(const ESM::MagicEffect *magicEffect, bool isNew, bool playSound) { // During first auto equip, we don't play any sounds. diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 7116b6ef1c..c33d511ecc 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -41,6 +41,7 @@ private: std::string mHairModel; ViewMode mViewMode; bool mShowWeapons; + bool mShowShield; int mVisibilityFlags; @@ -81,6 +82,7 @@ public: virtual Ogre::Vector3 runAnimation(float timepassed); virtual void showWeapons(bool showWeapon); + virtual void showShield(bool showShield); void setViewMode(ViewMode viewMode); From f3e89e916895f0e4b4ba38d2569b394464855516 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 23 Nov 2013 22:48:39 +0100 Subject: [PATCH 352/434] Fix arrow down in console --- apps/openmw/mwgui/console.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp index a1e3fb7381..96bc204c15 100644 --- a/apps/openmw/mwgui/console.cpp +++ b/apps/openmw/mwgui/console.cpp @@ -246,7 +246,7 @@ namespace MWGui { if(mCurrent != mCommandHistory.end()) { - --mCurrent; + ++mCurrent; if(mCurrent != mCommandHistory.end()) mCommandLine->setCaption(*mCurrent); From 4aa9f3bcefc07344e5be487420ab046210862309 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 23 Nov 2013 22:48:56 +0100 Subject: [PATCH 353/434] Don't set the enchanted item in HUD for "WhenStrikes" enchantments --- apps/openmw/mwmechanics/spellcasting.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 9f7073d2af..08db189778 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -312,7 +312,7 @@ namespace MWMechanics } if (enchantment->mData.mType == ESM::Enchantment::CastOnce) item.getContainerStore()->remove(item, 1, mCaster); - else + else if (enchantment->mData.mType != ESM::Enchantment::WhenStrikes) { if (mCaster.getRefData().getHandle() == "player") MWBase::Environment::get().getWindowManager()->setSelectedEnchantItem(item); // Set again to show the modified charge From 07408a4652f56ff777f7aee3aec6e2b8058e6cc2 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 23 Nov 2013 22:52:20 +0100 Subject: [PATCH 354/434] Don't allow selling gold (again - when did this get broken?) --- apps/openmw/mwclass/misc.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index 67f79c40ba..1a40c45554 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -241,7 +241,8 @@ namespace MWClass MWWorld::LiveCellRef *ref = item.get(); - return !ref->mBase->mData.mIsKey && (npcServices & ESM::NPC::Misc); + return !ref->mBase->mData.mIsKey && (npcServices & ESM::NPC::Misc) + && !Misc::StringUtils::ciEqual(item.getCellRef().mRefID, "gold_001"); } float Miscellaneous::getWeight(const MWWorld::Ptr &ptr) const From 14c9a4e1d391c7be655ff89ba9e3cec26a6f1d58 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 23 Nov 2013 23:12:54 +0100 Subject: [PATCH 355/434] Cap enchantment casting cost to 1 as displayed in enchanting window. Display current enchantment charge in spell window. --- apps/openmw/mwclass/npc.cpp | 2 +- apps/openmw/mwgui/spellwindow.cpp | 12 ++++++++++-- apps/openmw/mwmechanics/spellcasting.cpp | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 6970e8646e..8ff8081bc7 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -463,7 +463,7 @@ namespace MWClass // Check if we have enough charges const float enchantCost = enchantment->mData.mCost; int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified(); - const float castCost = enchantCost - (enchantCost / 100) * (eSkill - 10); + const int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10)); if (weapon.getCellRef().mEnchantmentCharge == -1) weapon.getCellRef().mEnchantmentCharge = enchantment->mData.mCharge; diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp index 03bb106310..42a0b98657 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -282,8 +282,16 @@ namespace MWGui MyGUI::Button* costCharge = mSpellView->createWidget(equipped ? "SpellText" : "SpellTextUnequipped", MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); - std::string cost = boost::lexical_cast(enchant->mData.mCost); - std::string charge = boost::lexical_cast(enchant->mData.mCharge); /// \todo track current charge + float enchantCost = enchant->mData.mCost; + MWMechanics::NpcStats &stats = player.getClass().getNpcStats(player); + int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified(); + int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10)); + + std::string cost = boost::lexical_cast(castCost); + int currentCharge = int(item.getCellRef().mEnchantmentCharge); + if (currentCharge == -1) + currentCharge = enchant->mData.mCharge; + std::string charge = boost::lexical_cast(currentCharge); if (enchant->mData.mType == ESM::Enchantment::CastOnce) { // this is Morrowind behaviour diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 08db189778..65efc0dab9 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -295,7 +295,7 @@ namespace MWMechanics const float enchantCost = enchantment->mData.mCost; MWMechanics::NpcStats &stats = MWWorld::Class::get(mCaster).getNpcStats(mCaster); int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified(); - const float castCost = enchantCost - (enchantCost / 100) * (eSkill - 10); + const int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10)); if (item.getCellRef().mEnchantmentCharge == -1) item.getCellRef().mEnchantmentCharge = enchantment->mData.mCharge; From 6b81fd78f18145a1c1aa1c626ca4b4ad4878b40f Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 25 Nov 2013 13:50:33 +0100 Subject: [PATCH 356/434] Particle improvements: Handle LocalSpace flag. Attach particle systems to the base node, since they need to be relative to that when LocalSpace is enabled. Get the bone in emitters/affectors so that resulting particle positions are the same. TODO: Fix Controllers to affect particle systems. --- components/nif/node.hpp | 3 +- components/nifogre/ogrenifloader.cpp | 61 +++++++------ components/nifogre/particles.cpp | 129 +++++++++++++++++++++++++-- 3 files changed, 159 insertions(+), 34 deletions(-) diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 917bc8add3..6816a79a2e 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -137,7 +137,8 @@ struct NiNode : Node AnimFlag_AutoPlay = 0x0020 }; enum BSParticleFlags { - ParticleFlag_AutoPlay = 0x0020 + ParticleFlag_AutoPlay = 0x0020, + ParticleFlag_LocalSpace = 0x0080 }; void read(NIFStream *nif) diff --git a/components/nifogre/ogrenifloader.cpp b/components/nifogre/ogrenifloader.cpp index bb98501f47..a530d060d2 100644 --- a/components/nifogre/ogrenifloader.cpp +++ b/components/nifogre/ogrenifloader.cpp @@ -497,7 +497,9 @@ class NIFObjectLoader } - static void createParticleEmitterAffectors(Ogre::ParticleSystem *partsys, const Nif::NiParticleSystemController *partctrl) + static void createParticleEmitterAffectors(Ogre::ParticleSystem *partsys, + const Nif::NiParticleSystemController *partctrl, Ogre::Bone* bone, + const std::string& skelBaseName) { Ogre::ParticleEmitter *emitter = partsys->addEmitter("Nif"); emitter->setParticleVelocity(partctrl->velocity - partctrl->velocityRandom*0.5f, @@ -512,6 +514,8 @@ class NIFObjectLoader emitter->setParameter("vertical_angle", Ogre::StringConverter::toString(Ogre::Radian(partctrl->verticalAngle).valueDegrees())); emitter->setParameter("horizontal_direction", Ogre::StringConverter::toString(Ogre::Radian(partctrl->horizontalDir).valueDegrees())); emitter->setParameter("horizontal_angle", Ogre::StringConverter::toString(Ogre::Radian(partctrl->horizontalAngle).valueDegrees())); + emitter->setParameter("skelbase", skelBaseName); + emitter->setParameter("bone", bone->getName()); Nif::ExtraPtr e = partctrl->extra; while(!e.empty()) @@ -533,6 +537,8 @@ class NIFObjectLoader affector->setParameter("force_type", (gr->mType==0) ? "wind" : "point"); affector->setParameter("direction", Ogre::StringConverter::toString(gr->mDirection)); affector->setParameter("position", Ogre::StringConverter::toString(gr->mPosition)); + affector->setParameter("skelbase", skelBaseName); + affector->setParameter("bone", bone->getName()); } else if(e->recType == Nif::RC_NiParticleColorModifier) { @@ -565,7 +571,7 @@ class NIFObjectLoader } static void createParticleSystem(const std::string &name, const std::string &group, - Ogre::SceneManager *sceneMgr, ObjectList &objectlist, + Ogre::SceneNode *sceneNode, ObjectList &objectlist, const Nif::Node *partnode, int flags, int partflags) { const Nif::NiAutoNormalParticlesData *particledata = NULL; @@ -579,7 +585,7 @@ class NIFObjectLoader fullname += "@type="+partnode->name; Misc::StringUtils::toLower(fullname); - Ogre::ParticleSystem *partsys = sceneMgr->createParticleSystem(); + Ogre::ParticleSystem *partsys = sceneNode->getCreator()->createParticleSystem(); const Nif::NiTexturingProperty *texprop = NULL; const Nif::NiMaterialProperty *matprop = NULL; @@ -600,9 +606,9 @@ class NIFObjectLoader particledata->particleRadius*2.0f); partsys->setCullIndividually(false); partsys->setParticleQuota(particledata->numParticles); - // TODO: There is probably a field or flag to specify this, as some - // particle effects have it and some don't. - partsys->setKeepParticlesInLocalSpace(false); + partsys->setKeepParticlesInLocalSpace(partflags & (Nif::NiNode::ParticleFlag_LocalSpace)); + + sceneNode->attachObject(partsys); Nif::ControllerPtr ctrl = partnode->controller; while(!ctrl.empty()) @@ -611,12 +617,11 @@ class NIFObjectLoader { const Nif::NiParticleSystemController *partctrl = static_cast(ctrl.getPtr()); - createParticleEmitterAffectors(partsys, partctrl); - if(!partctrl->emitter.empty() && !partsys->isAttached()) + if(!partctrl->emitter.empty()) { int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, partctrl->emitter->recIndex); Ogre::Bone *trgtbone = objectlist.mSkelBase->getSkeleton()->getBone(trgtid); - objectlist.mSkelBase->attachObjectToBone(trgtbone->getName(), partsys); + createParticleEmitterAffectors(partsys, partctrl, trgtbone, objectlist.mSkelBase->getName()); } Ogre::ControllerValueRealPtr srcval((partflags&Nif::NiNode::ParticleFlag_AutoPlay) ? @@ -634,13 +639,6 @@ class NIFObjectLoader ctrl = ctrl->next; } - if(!partsys->isAttached()) - { - int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, partnode->recIndex); - Ogre::Bone *trgtbone = objectlist.mSkelBase->getSkeleton()->getBone(trgtid); - objectlist.mSkelBase->attachObjectToBone(trgtbone->getName(), partsys); - } - partsys->setVisible(!(flags&Nif::NiNode::Flag_Hidden)); objectlist.mParticles.push_back(partsys); } @@ -729,7 +727,7 @@ class NIFObjectLoader static void createObjects(const std::string &name, const std::string &group, - Ogre::SceneManager *sceneMgr, const Nif::Node *node, + Ogre::SceneNode *sceneNode, const Nif::Node *node, ObjectList &objectlist, int flags, int animflags, int partflags) { // Do not create objects for the collision shape (includes all children) @@ -784,13 +782,13 @@ class NIFObjectLoader if(node->recType == Nif::RC_NiTriShape && !(flags&0x80000000)) { - createEntity(name, group, sceneMgr, objectlist, node, flags, animflags); + createEntity(name, group, sceneNode->getCreator(), objectlist, node, flags, animflags); } if((node->recType == Nif::RC_NiAutoNormalParticles || node->recType == Nif::RC_NiRotatingParticles) && !(flags&0x40000000)) { - createParticleSystem(name, group, sceneMgr, objectlist, node, flags, partflags); + createParticleSystem(name, group, sceneNode, objectlist, node, flags, partflags); } const Nif::NiNode *ninode = dynamic_cast(node); @@ -800,7 +798,7 @@ class NIFObjectLoader for(size_t i = 0;i < children.length();i++) { if(!children[i].empty()) - createObjects(name, group, sceneMgr, children[i].getPtr(), objectlist, flags, animflags, partflags); + createObjects(name, group, sceneNode, children[i].getPtr(), objectlist, flags, animflags, partflags); } } } @@ -821,7 +819,7 @@ class NIFObjectLoader } public: - static void load(Ogre::SceneManager *sceneMgr, ObjectList &objectlist, const std::string &name, const std::string &group, int flags=0) + static void load(Ogre::SceneNode *sceneNode, ObjectList &objectlist, const std::string &name, const std::string &group, int flags=0) { Nif::NIFFile::ptr nif = Nif::NIFFile::create(name); if(nif->numRoots() < 1) @@ -845,9 +843,9 @@ public: !NIFSkeletonLoader::createSkeleton(name, group, node).isNull()) { // Create a base skeleton entity if this NIF needs one - createSkelBase(name, group, sceneMgr, node, objectlist); + createSkelBase(name, group, sceneNode->getCreator(), node, objectlist); } - createObjects(name, group, sceneMgr, node, objectlist, flags, 0, 0); + createObjects(name, group, sceneNode, node, objectlist, flags, 0, 0); } static void loadKf(Ogre::Skeleton *skel, const std::string &name, @@ -915,7 +913,7 @@ ObjectList Loader::createObjects(Ogre::SceneNode *parentNode, std::string name, ObjectList objectlist; Misc::StringUtils::toLower(name); - NIFObjectLoader::load(parentNode->getCreator(), objectlist, name, group); + NIFObjectLoader::load(parentNode, objectlist, name, group); for(size_t i = 0;i < objectlist.mEntities.size();i++) { @@ -934,7 +932,7 @@ ObjectList Loader::createObjects(Ogre::Entity *parent, const std::string &bonena ObjectList objectlist; Misc::StringUtils::toLower(name); - NIFObjectLoader::load(parentNode->getCreator(), objectlist, name, group); + NIFObjectLoader::load(parentNode, objectlist, name, group); bool isskinned = false; for(size_t i = 0;i < objectlist.mEntities.size();i++) @@ -984,6 +982,17 @@ ObjectList Loader::createObjects(Ogre::Entity *parent, const std::string &bonena } } + for(size_t i = 0;i < objectlist.mParticles.size();i++) + { + Ogre::ParticleSystem *partsys = objectlist.mParticles[i]; + if(partsys->isAttached()) + partsys->detachFromParent(); + + Ogre::TagPoint *tag = objectlist.mSkelBase->attachObjectToBone( + objectlist.mSkelBase->getSkeleton()->getRootBone()->getName(), partsys); + tag->setScale(scale); + } + return objectlist; } @@ -993,7 +1002,7 @@ ObjectList Loader::createObjectBase(Ogre::SceneNode *parentNode, std::string nam ObjectList objectlist; Misc::StringUtils::toLower(name); - NIFObjectLoader::load(parentNode->getCreator(), objectlist, name, group, 0xC0000000); + NIFObjectLoader::load(parentNode, objectlist, name, group, 0xC0000000); if(objectlist.mSkelBase) parentNode->attachObject(objectlist.mSkelBase); diff --git a/components/nifogre/particles.cpp b/components/nifogre/particles.cpp index 707bd75e08..006a570dce 100644 --- a/components/nifogre/particles.cpp +++ b/components/nifogre/particles.cpp @@ -5,11 +5,55 @@ #include #include #include +#include +#include +#include +#include +#include +#include /* FIXME: "Nif" isn't really an appropriate emitter name. */ class NifEmitter : public Ogre::ParticleEmitter { public: + std::string mSkelBaseName; + Ogre::Bone* mBone; + + Ogre::ParticleSystem* getPartSys() { return mParent; } + + class CmdSkelBase : public Ogre::ParamCommand + { + public: + Ogre::String doGet(const void *target) const + { + assert(false && "Unimplemented"); + } + void doSet(void *target, const Ogre::String &val) + { + NifEmitter* emitter = static_cast(target); + emitter->mSkelBaseName = val; + } + }; + + class CmdBone : public Ogre::ParamCommand + { + public: + Ogre::String doGet(const void *target) const + { + assert(false && "Unimplemented"); + } + void doSet(void *target, const Ogre::String &val) + { + NifEmitter* emitter = static_cast(target); + assert(!emitter->mSkelBaseName.empty() && "Base entity needs to be set first"); + Ogre::ParticleSystem* partsys = emitter->getPartSys(); + Ogre::Entity* ent = partsys->getParentSceneNode()->getCreator()->getEntity(emitter->mSkelBaseName); + Ogre::Bone* bone = ent->getSkeleton()->getBone(val); + assert(bone); + emitter->mBone = bone; + } + }; + /** Command object for the emitter width (see Ogre::ParamCommand).*/ class CmdWidth : public Ogre::ParamCommand { @@ -119,6 +163,7 @@ public: NifEmitter(Ogre::ParticleSystem *psys) : Ogre::ParticleEmitter(psys) + , mBone(NULL) { initDefaults("Nif"); } @@ -133,6 +178,7 @@ public: /** See Ogre::ParticleEmitter. */ void _initParticle(Ogre::Particle *particle) { + assert (mBone && "No node set"); Ogre::Vector3 xOff, yOff, zOff; // Call superclass @@ -141,8 +187,8 @@ public: xOff = Ogre::Math::SymmetricRandom() * mXRange; yOff = Ogre::Math::SymmetricRandom() * mYRange; zOff = Ogre::Math::SymmetricRandom() * mZRange; - - particle->position = mPosition + xOff + yOff + zOff; + + particle->position = mBone->_getDerivedPosition() + xOff + yOff + zOff; // Generate complex data by reference genEmissionColour(particle->colour); @@ -150,7 +196,7 @@ public: // NOTE: We do not use mDirection/mAngle for the initial direction. Ogre::Radian hdir = mHorizontalDir + mHorizontalAngle*Ogre::Math::SymmetricRandom(); Ogre::Radian vdir = mVerticalDir + mVerticalAngle*Ogre::Math::SymmetricRandom(); - particle->direction = (Ogre::Quaternion(hdir, Ogre::Vector3::UNIT_Z) * + particle->direction = (mBone->_getDerivedOrientation() * Ogre::Quaternion(hdir, Ogre::Vector3::UNIT_Z) * Ogre::Quaternion(vdir, Ogre::Vector3::UNIT_X)) * Ogre::Vector3::UNIT_Z; @@ -313,6 +359,16 @@ protected: Ogre::PT_REAL), &msHorizontalAngleCmd); + dict->addParameter(Ogre::ParameterDef("bone", + "The bone where the particles should be spawned", + Ogre::PT_STRING), + &msBoneCmd); + + dict->addParameter(Ogre::ParameterDef("skelbase", + "The name of the entity containing the bone (see 'bone' parameter)", + Ogre::PT_STRING), + &msSkelBaseCmd); + return true; } return false; @@ -326,6 +382,8 @@ protected: static CmdVerticalAngle msVerticalAngleCmd; static CmdHorizontalDir msHorizontalDirCmd; static CmdHorizontalAngle msHorizontalAngleCmd; + static CmdBone msBoneCmd; + static CmdSkelBase msSkelBaseCmd; }; NifEmitter::CmdWidth NifEmitter::msWidthCmd; NifEmitter::CmdHeight NifEmitter::msHeightCmd; @@ -334,12 +392,14 @@ NifEmitter::CmdVerticalDir NifEmitter::msVerticalDirCmd; NifEmitter::CmdVerticalAngle NifEmitter::msVerticalAngleCmd; NifEmitter::CmdHorizontalDir NifEmitter::msHorizontalDirCmd; NifEmitter::CmdHorizontalAngle NifEmitter::msHorizontalAngleCmd; +NifEmitter::CmdBone NifEmitter::msBoneCmd; +NifEmitter::CmdSkelBase NifEmitter::msSkelBaseCmd; Ogre::ParticleEmitter* NifEmitterFactory::createEmitter(Ogre::ParticleSystem *psys) { - Ogre::ParticleEmitter *emit = OGRE_NEW NifEmitter(psys); - mEmitters.push_back(emit); - return emit; + Ogre::ParticleEmitter *emitter = OGRE_NEW NifEmitter(psys); + mEmitters.push_back(emitter); + return emitter; } @@ -492,6 +552,45 @@ class GravityAffector : public Ogre::ParticleAffector }; public: + std::string mSkelBaseName; + Ogre::Bone* mBone; + + Ogre::ParticleSystem* getPartSys() { return mParent; } + + class CmdSkelBase : public Ogre::ParamCommand + { + public: + Ogre::String doGet(const void *target) const + { + assert(false && "Unimplemented"); + } + void doSet(void *target, const Ogre::String &val) + { + GravityAffector* affector = static_cast(target); + affector->mSkelBaseName = val; + } + }; + + class CmdBone : public Ogre::ParamCommand + { + public: + Ogre::String doGet(const void *target) const + { + assert(false && "Unimplemented"); + } + void doSet(void *target, const Ogre::String &val) + { + GravityAffector* affector = static_cast(target); + assert(!affector->mSkelBaseName.empty() && "Base entity needs to be set first"); + Ogre::ParticleSystem* partsys = affector->getPartSys(); + Ogre::Entity* ent = partsys->getParentSceneNode()->getCreator()->getEntity(affector->mSkelBaseName); + Ogre::Bone* bone = ent->getSkeleton()->getBone(val); + assert(bone); + affector->mBone = bone; + } + }; + + /** Command object for force (see Ogre::ParamCommand).*/ class CmdForce : public Ogre::ParamCommand { @@ -585,6 +684,7 @@ public: , mForceType(Type_Wind) , mPosition(0.0f) , mDirection(0.0f) + , mBone(NULL) { mType = "Gravity"; @@ -606,6 +706,16 @@ public: dict->addParameter(Ogre::ParameterDef(force_type_title, force_type_descr, Ogre::PT_STRING), &msForceTypeCmd); dict->addParameter(Ogre::ParameterDef(direction_title, direction_descr, Ogre::PT_VECTOR3), &msDirectionCmd); dict->addParameter(Ogre::ParameterDef(position_title, position_descr, Ogre::PT_VECTOR3), &msPositionCmd); + + dict->addParameter(Ogre::ParameterDef("bone", + "The bone where the particles should be spawned", + Ogre::PT_STRING), + &msBoneCmd); + + dict->addParameter(Ogre::ParameterDef("skelbase", + "The name of the entity containing the bone (see 'bone' parameter)", + Ogre::PT_STRING), + &msSkelBaseCmd); } } @@ -647,6 +757,8 @@ public: static CmdForceType msForceTypeCmd; static CmdDirection msDirectionCmd; static CmdPosition msPositionCmd; + static CmdBone msBoneCmd; + static CmdSkelBase msSkelBaseCmd; protected: void applyWindForce(Ogre::ParticleSystem *psys, Ogre::Real timeElapsed) @@ -667,7 +779,8 @@ protected: while (!pi.end()) { Ogre::Particle *p = pi.getNext(); - const Ogre::Vector3 vec = (p->position - mPosition).normalisedCopy() * force; + const Ogre::Vector3 vec = ( + (mBone->_getDerivedOrientation() * mPosition + mBone->_getDerivedPosition()) - p->position).normalisedCopy() * force; p->direction += vec; } } @@ -684,6 +797,8 @@ GravityAffector::CmdForce GravityAffector::msForceCmd; GravityAffector::CmdForceType GravityAffector::msForceTypeCmd; GravityAffector::CmdDirection GravityAffector::msDirectionCmd; GravityAffector::CmdPosition GravityAffector::msPositionCmd; +GravityAffector::CmdBone GravityAffector::msBoneCmd; +GravityAffector::CmdSkelBase GravityAffector::msSkelBaseCmd; Ogre::ParticleAffector *GravityAffectorFactory::createAffector(Ogre::ParticleSystem *psys) { From 58dce88c7d1a93d3bb784dc479eca845c7f1bf31 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 25 Nov 2013 14:03:51 +0100 Subject: [PATCH 357/434] Handle the "tai" alias for ToggleAI --- components/compiler/extensions0.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index 01792def95..de1a2cf299 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -60,6 +60,7 @@ namespace Compiler extensions.registerInstruction ("modflee", "l", opcodeModFlee, opcodeModFleeExplicit); extensions.registerInstruction ("modalarm", "l", opcodeModAlarm, opcodeModAlarmExplicit); extensions.registerInstruction ("toggleai", "", opcodeToggleAI, opcodeToggleAI); + extensions.registerInstruction ("tai", "", opcodeToggleAI, opcodeToggleAI); extensions.registerFunction ("gethello", 'l', "", opcodeGetHello, opcodeGetHelloExplicit); extensions.registerFunction ("getfight", 'l', "", opcodeGetFight, opcodeGetFightExplicit); extensions.registerFunction ("getflee", 'l', "", opcodeGetFlee, opcodeGetFleeExplicit); From 9f5ff033d720335fa6329915455addbfab01cf63 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 25 Nov 2013 14:06:59 +0100 Subject: [PATCH 358/434] Handle the "GetLOS" alias for GetLineOfSight --- components/compiler/extensions0.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index de1a2cf299..e95f6f6985 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -66,6 +66,7 @@ namespace Compiler extensions.registerFunction ("getflee", 'l', "", opcodeGetFlee, opcodeGetFleeExplicit); extensions.registerFunction ("getalarm", 'l', "", opcodeGetAlarm, opcodeGetAlarmExplicit); extensions.registerFunction ("getlineofsight", 'l', "c", opcodeGetLineOfSight, opcodeGetLineOfSightExplicit); + extensions.registerFunction ("getlos", 'l', "c", opcodeGetLineOfSight, opcodeGetLineOfSightExplicit); } } From eba068149d13b9cd6dbd9afdd7af2fc25b762033 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 25 Nov 2013 15:38:18 +0100 Subject: [PATCH 359/434] Fix AIWander crash --- apps/openmw/mwmechanics/aiwander.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index ecc41489a5..f66f7ca620 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -144,12 +144,12 @@ namespace MWMechanics mCurrentNode = mAllowedNodes[index]; mAllowedNodes.erase(mAllowedNodes.begin() + index); } - - if(mAllowedNodes.empty()) - mDistance = 0; } } + if(mAllowedNodes.empty()) + mDistance = 0; + // Don't try to move if you are in a new cell (ie: positioncell command called) but still play idles. if(mDistance && (mCellX != actor.getCell()->mCell->mData.mX || mCellY != actor.getCell()->mCell->mData.mY)) mDistance = 0; @@ -200,6 +200,7 @@ namespace MWMechanics { if(!mPathFinder.isPathConstructed()) { + assert(mAllowedNodes.size()); unsigned int randNode = (int)(rand() / ((double)RAND_MAX + 1) * mAllowedNodes.size()); Ogre::Vector3 destNodePos(mAllowedNodes[randNode].mX, mAllowedNodes[randNode].mY, mAllowedNodes[randNode].mZ); From bda28e2ed0e2e298f460c0b8cbddb1fd613b35e6 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 26 Nov 2013 06:42:37 +0100 Subject: [PATCH 360/434] Increase size of item selection dialog --- files/mygui/openmw_itemselection_dialog.layout | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/files/mygui/openmw_itemselection_dialog.layout b/files/mygui/openmw_itemselection_dialog.layout index 003eb1c6ac..5fc2862981 100644 --- a/files/mygui/openmw_itemselection_dialog.layout +++ b/files/mygui/openmw_itemselection_dialog.layout @@ -1,13 +1,13 @@ - + - + - + From 49ea1aae67ae5d987e96209e2b79d3db6072c27a Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 26 Nov 2013 15:01:22 +0100 Subject: [PATCH 361/434] Use GMST for sun damage reduction --- apps/openmw/mwmechanics/actors.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 5aa846118b..def5708a62 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -258,9 +258,12 @@ namespace MWMechanics float timeDiff = std::min(7.f, std::max(0.f, std::abs(time - 13))); float damageScale = 1.f - timeDiff / 7.f; // When cloudy, the sun damage effect is halved + static float fMagicSunBlockedMult = MWBase::Environment::get().getWorld()->getStore().get().find( + "fMagicSunBlockedMult")->getFloat(); + int weather = MWBase::Environment::get().getWorld()->getCurrentWeather(); if (weather > 1) - damageScale *= 0.5; + damageScale *= fMagicSunBlockedMult; health.setCurrent(health.getCurrent() - magnitude * duration * damageScale); } else From 400831faf82bd31a4ba123df89899eef65ac66ad Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Tue, 26 Nov 2013 15:51:59 +0100 Subject: [PATCH 362/434] Added short overiview of resources avaible. Thanks for assistance and insight on this, serpentine, lgro and scrawl. --- manual/opencs/files_and_directories.tex | 29 +++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/manual/opencs/files_and_directories.tex b/manual/opencs/files_and_directories.tex index 032a7cdce4..b4af951b39 100644 --- a/manual/opencs/files_and_directories.tex +++ b/manual/opencs/files_and_directories.tex @@ -35,15 +35,40 @@ Since addon is supposed to change the game it is logical that it also depends on Game files are not intend to have any dependencies for a very simple reasons: player is using only one game file (excluding original and dirty esp/esm system) at the time and therefore no game file can depend on other game file, and since game file makes the base for addon files -- it can't depend on addon files.\\ \subparagraph{Loading order} +%TODO \paragraph{Project files} Project files act as containers for data not used by the Openm{MW} game engine itself, but still useful for OpenCS. The shining example of this data category are without doubt record filters (described in the later section of the manual you are reading currently). As a mod author you probably don't need and/or want to distribute project files at all, they are meant to be used only by you.\\ As you would imagine, project file makes sense only in combination with actual content files. In fact, each time you start to work on new content file and project file was not found, it will be created.\\ -Project files extension is, to not surprise ``.project''. The whole name of the project file is the whole name of the content file with appended extensions. For instance awesomeswords.omwaddon file is associated with awesomeswords.omwaddon.project file.\\ +Project files extension is, to not surprise ``.project''. The whole name of the project file is the whole name of the content file with appended extensions. For instance swords.omwaddon file is associated with swords.omwaddon.project file.\\ %TODO where are they stored. Project files are stored inside the user directory, in the \textbf{projects} subfolder. This is the path location for both freshly created project files, and a place where Open{CS} looks for already existing files.\\ \paragraph{Resources files} %textures, sounds, whatever -Unless we are talking about the fully text based game, like Zork or Rogue, you are expecting that a video game is using some media files: models with textures, pictures acting as icons, sounds and everything else. Since content files, no matter if it is esp, esm or new Open{MW} file type do not contain any of those, it's clear that they have to be deliver with a different file. It is also clear that this, let's call it ``resources file``, have to be supported by the engine. Without code handling those files, it is nothing more than a mathematical abstraction -- something, that lacks meaning for human beings\footnote{Unless we call programmers a human beings.}. Therefore this section must cover ways to add resources files to your content file, and point out what is supported. We are going to do just that. Later, you will learn how to make use of those files in your content. \ No newline at end of file +Unless we are talking about the fully text based game, like Zork or Rogue, you are expecting that a video game is using some media files: models with textures, pictures acting as icons, sounds and everything else. Since content files, no matter if it is esp, esm or new Open{MW} file type do not contain any of those, it's clear that they have to be deliver with a different file. It is also clear that this, let's call it ``resources file``, have to be supported by the engine. Without code handling those files, it is nothing more than a mathematical abstraction -- something, that lacks meaning for human beings\footnote{Unless we call programmers a human beings.}. Therefore this section must cover ways to add resources files to your content file, and point out what is supported. We are going to do just that. Later, you will learn how to make use of those files in your content. + +\subparagraph{Audio} +Open{MW} is using {FF}mpeg for audio playback, and so we support every audio type that is supported by this library. This makes a huge list. Below is only small portion of supported file types. + +\begin{description} + \item mp3 popular format and \textit{de facto} standard for storing audio. Used by the Morrowind game. + \item mp4 format with a better compression rate than mp3, but also requiring more {CPU} intensive decoding -- this makes it probably less suited for video games. + \item ogg open source, audio container file using high quality vorbis codec. Recommended. +\end{description} + + +\subparagraph{Video} +As in the case of audio files, we are using {FFmepg} to decode video files. The list of supported files is long, we will cover only the most significant. + +\begin{description} + \item bik videos used by original Morrowind game. + \item webm is a new, shiny and open source video format with excellent compression. It needs quite a lot of processing power to be decoded, but since game logic is not running during cut scenes we can recommended it for use with Open{MW}. + \item ogv alternative, open source container using theora codec for video and vorbis for audio. +\end{description} + +\subparagraph{Textures and images} +Original Morrowind game uses dds and tga file for all kind of two dimensional images and textures alike. In addition, engine supported bmp files for some reason (bmp is a terrible format for a video game). We also support extended set of image files -- including jpeg and png. Jpeg and png files can be useful in some cases, for instance jpeg file is a valid option for skybox texture and png can useful for masks. However please, keep in mind that jpeg can grow into large sizes quickly and are not the best option with directx rendering backend. + +\subparagraph{Meshes} %TODO once we will support something more than just nifs \ No newline at end of file From a8c838b53a056353e6871824602a66a0120873fc Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 27 Nov 2013 18:43:42 +0100 Subject: [PATCH 363/434] Don't list deleted refs in CellStore::foreach --- apps/openmw/mwworld/cellstore.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index bcbc5e415a..8a01caf183 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -140,9 +140,12 @@ namespace MWWorld { for (typename List::List::iterator iter (list.mList.begin()); iter!=list.mList.end(); ++iter) + { + if (!iter->mData.getCount()) + continue; if (!functor (iter->mRef, iter->mData)) return false; - + } return true; } From 03c4b680ca1f506ebb0bc8d77d3ceba5d86f1f37 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 27 Nov 2013 18:45:46 +0100 Subject: [PATCH 364/434] Fix changePointer --- apps/openmw/mwgui/windowmanagerimp.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index dc753a8fa7..2bcfdfb62d 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1304,6 +1304,7 @@ namespace MWGui void WindowManager::changePointer(const std::string &name) { + MyGUI::PointerManager::getInstance().setPointer(name); onCursorChange(name); } From 97fadb24ca5b97cdced575612ba269e8d205f74e Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 27 Nov 2013 18:46:18 +0100 Subject: [PATCH 365/434] Update the Ptr in mObjects in Objects::updateObjectCell --- apps/openmw/mwrender/objects.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index 4d5f6872df..827b9b52a7 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -278,7 +278,17 @@ void Objects::updateObjectCell(const MWWorld::Ptr &old, const MWWorld::Ptr &cur) } else { node = mCellSceneNodes[newCell]; } + node->addChild(cur.getRefData().getBaseNode()); + + PtrAnimationMap::iterator iter = mObjects.find(old); + if(iter != mObjects.end()) + { + ObjectAnimation *anim = iter->second; + mObjects.erase(iter); + anim->updatePtr(cur); + mObjects[cur] = anim; + } } ObjectAnimation* Objects::getAnimation(const MWWorld::Ptr &ptr) From 4a0a16be364f2e04d1459f8b91ea266821a87bfb Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 10 Nov 2013 22:40:46 +0100 Subject: [PATCH 366/434] Fix a build error --- components/contentselector/model/contentmodel.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 5f3575eb40..9183003295 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -1,6 +1,8 @@ #include "contentmodel.hpp" #include "esmfile.hpp" +#include + #include #include #include From 20ccfe2324717a6a559555ec1e6f5d26f933b4fb Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 28 Nov 2013 11:37:30 +0100 Subject: [PATCH 367/434] Play sound when enchanting --- apps/openmw/mwgui/enchantingdialog.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp index 98ba8ec2f2..d2e914d17e 100644 --- a/apps/openmw/mwgui/enchantingdialog.cpp +++ b/apps/openmw/mwgui/enchantingdialog.cpp @@ -4,6 +4,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "../mwbase/soundmanager.hpp" #include "../mwworld/player.hpp" #include "../mwworld/manualref.hpp" #include "../mwworld/class.hpp" @@ -298,9 +299,15 @@ namespace MWGui int result = mEnchanting.create(); if(result==1) + { + MWBase::Environment::get().getSoundManager()->playSound("enchant success", 1.f, 1.f); MWBase::Environment::get().getWindowManager()->messageBox ("#{sEnchantmentMenu12}"); + } else + { + MWBase::Environment::get().getSoundManager()->playSound("enchant fail", 1.f, 1.f); MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage34}"); + } MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Enchanting); } From 076cc9230b4ea9d870e74dcee31a1cf8d57d6d1a Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 28 Nov 2013 17:31:17 +0100 Subject: [PATCH 368/434] First try at handling target magic --- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwmechanics/spellcasting.cpp | 4 +- apps/openmw/mwmechanics/spellcasting.hpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 109 ++++++++++++++++++++--- apps/openmw/mwworld/worldimp.hpp | 11 ++- 5 files changed, 111 insertions(+), 17 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 8b49463080..c092840dd8 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -417,7 +417,7 @@ namespace MWBase virtual void castSpell (const MWWorld::Ptr& actor) = 0; - virtual void launchProjectile (const std::string& id, const ESM::EffectList& effects, + virtual void launchProjectile (const std::string& id, bool stack, const ESM::EffectList& effects, const MWWorld::Ptr& actor, const std::string& sourceName) = 0; }; } diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 65efc0dab9..74816d12e2 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -329,7 +329,7 @@ namespace MWMechanics inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Touch); } - MWBase::Environment::get().getWorld()->launchProjectile(mId, enchantment->mEffects, mCaster, mSourceName); + MWBase::Environment::get().getWorld()->launchProjectile(mId, false, enchantment->mEffects, mCaster, mSourceName); return true; } @@ -434,7 +434,7 @@ namespace MWMechanics } } - MWBase::Environment::get().getWorld()->launchProjectile(mId, spell->mEffects, mCaster, mSourceName); + MWBase::Environment::get().getWorld()->launchProjectile(mId, false, spell->mEffects, mCaster, mSourceName); return true; } diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index c40567fcfa..e2efaa140b 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -184,7 +184,7 @@ namespace MWMechanics private: MWWorld::Ptr mCaster; MWWorld::Ptr mTarget; - + public: bool mStack; std::string mId; // ID of spell, potion, item etc std::string mSourceName; // Display name for spell, potion, etc diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 3c49b87390..dcf0d7ca99 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -804,6 +804,8 @@ namespace MWWorld return MWWorld::Ptr (); MWWorld::Ptr object = searchPtrViaHandle (result.second); + if (object.isEmpty()) + return object; float ActivationDistance; if (MWBase::Environment::get().getWindowManager()->isConsoleMode()) @@ -895,15 +897,16 @@ namespace MWWorld copyObjectToCell(ptr, newCell, pos); else if (!mWorldScene->isCellActive(newCell)) { - MWWorld::Class::get(ptr) - .copyToCell(ptr, newCell) - .getRefData() - .setBaseNode(0); - mWorldScene->removeObjectFromScene(ptr); mLocalScripts.remove(ptr); removeContainerScripts (ptr); haveToMove = false; + + MWWorld::Ptr newPtr = MWWorld::Class::get(ptr) + .copyToCell(ptr, newCell); + newPtr.getRefData().setBaseNode(0); + + objectLeftActiveCell(ptr, newPtr); } else { @@ -1292,7 +1295,8 @@ namespace MWWorld mWorldScene->update (duration, paused); - doPhysics (duration); + if (!paused) + doPhysics (duration); performUpdateSceneQueries (); @@ -2066,11 +2070,12 @@ namespace MWWorld } } - void World::launchProjectile (const std::string& id, const ESM::EffectList& effects, + void World::launchProjectile (const std::string& id, bool stack, const ESM::EffectList& effects, const MWWorld::Ptr& actor, const std::string& sourceName) { std::string projectileModel; std::string sound; + float speed = 0; for (std::vector::const_iterator iter (effects.mList.begin()); iter!=effects.mList.end(); ++iter) { @@ -2091,18 +2096,20 @@ namespace MWWorld else sound = schools[magicEffect->mData.mSchool] + " bolt"; + speed = magicEffect->mData.mSpeed; break; } if (projectileModel.empty()) return; - return; + // Spawn at 0.75 * ActorHeight + float height = mPhysEngine->getCharacter(actor.getRefData().getHandle())->getHalfExtents().z * 2 * 0.75; MWWorld::ManualRef ref(getStore(), projectileModel); ESM::Position pos; pos.pos[0] = actor.getRefData().getPosition().pos[0]; pos.pos[1] = actor.getRefData().getPosition().pos[1]; - pos.pos[2] = actor.getRefData().getPosition().pos[2]; + pos.pos[2] = actor.getRefData().getPosition().pos[2] + height; pos.rot[0] = actor.getRefData().getPosition().rot[0]; pos.rot[1] = actor.getRefData().getPosition().rot[1]; pos.rot[2] = actor.getRefData().getPosition().rot[2]; @@ -2113,6 +2120,9 @@ namespace MWWorld state.mSourceName = sourceName; state.mId = id; state.mActorHandle = actor.getRefData().getHandle(); + state.mSpeed = speed; + state.mEffects = effects; + state.mStack = stack; MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); sndMgr->playSound3D(ptr, sound, 1.0f, 1.0f); @@ -2122,6 +2132,7 @@ namespace MWWorld void World::moveProjectiles(float duration) { + std::map moved; for (std::map::iterator it = mProjectiles.begin(); it != mProjectiles.end();) { if (!mWorldScene->isCellActive(*it->first.getCell())) @@ -2129,9 +2140,83 @@ namespace MWWorld mProjectiles.erase(it++); continue; } - // TODO: Move - //moveObject(it->first, newPos.x, newPos.y, newPos.z); - ++it; + + MWWorld::Ptr ptr = it->first; + + Ogre::Vector3 rot(ptr.getRefData().getPosition().rot); + + // TODO: Why -rot.z, but not -rot.x? + Ogre::Quaternion orient = Ogre::Quaternion(Ogre::Radian(-rot.z), Ogre::Vector3::UNIT_Z); + orient = orient * Ogre::Quaternion(Ogre::Radian(rot.x), Ogre::Vector3::UNIT_X); + + // This is just a guess, probably wrong + static float fProjectileMinSpeed = getStore().get().find("fProjectileMinSpeed")->getFloat(); + static float fProjectileMaxSpeed = getStore().get().find("fProjectileMaxSpeed")->getFloat(); + float speed = fProjectileMinSpeed + (fProjectileMaxSpeed - fProjectileMinSpeed) * it->second.mSpeed; + + Ogre::Vector3 direction = orient.yAxis(); + direction.normalise(); + Ogre::Vector3 pos(ptr.getRefData().getPosition().pos); + Ogre::Vector3 newPos = pos + direction * duration * speed; + + // Check for impact + btVector3 from(pos.x, pos.y, pos.z); + btVector3 to(newPos.x, newPos.y, newPos.z); + std::vector > collisions = mPhysEngine->rayTest2(from, to); + for (std::vector >::iterator cIt = collisions.begin(); cIt != collisions.end(); ++cIt) + { + MWWorld::Ptr obstacle = searchPtrViaHandle(cIt->second); + if (obstacle.isEmpty()) + { + // Terrain. TODO: Explode + continue; + } + if (obstacle == ptr) + continue; + MWWorld::Ptr caster = searchPtrViaHandle(it->second.mActorHandle); + if (caster.isEmpty()) + caster = obstacle; + MWMechanics::CastSpell cast(caster, obstacle); + cast.mStack = it->second.mStack; + cast.mId = it->second.mId; + cast.mSourceName = it->second.mSourceName; + cast.inflict(obstacle, caster, it->second.mEffects, ESM::RT_Target, false); + + deleteObject(ptr); + mProjectiles.erase(it++); + continue; + } + + std::string handle = ptr.getRefData().getHandle(); + + moveObject(ptr, newPos.x, newPos.y, newPos.z); + + // HACK: Re-fetch Ptrs if necessary, since the cell might have changed + if (!ptr.getRefData().getCount()) + { + moved[handle] = it->second; + mProjectiles.erase(it++); + } + else + ++it; + } + + // HACK: Re-fetch Ptrs if necessary, since the cell might have changed + for (std::map::iterator it = moved.begin(); it != moved.end(); ++it) + { + MWWorld::Ptr newPtr = searchPtrViaHandle(it->first); + if (newPtr.isEmpty()) // The projectile went into an inactive cell and was deleted + continue; + mProjectiles[getPtrViaHandle(it->first)] = it->second; + } + } + + void World::objectLeftActiveCell(Ptr object, Ptr movedPtr) + { + // For now, projectiles moved to an inactive cell are just deleted, because there's no reliable way to hold on to the meta information + if (mProjectiles.find(object) != mProjectiles.end()) + { + deleteObject(movedPtr); } } } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 1022a74fee..80119e014f 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -97,6 +97,12 @@ namespace MWWorld // Name of item to display as effect source in magic menu (in case we casted an enchantment) std::string mSourceName; + + ESM::EffectList mEffects; + + float mSpeed; + + bool mStack; }; std::map mProjectiles; @@ -147,6 +153,9 @@ namespace MWWorld bool mTeleportEnabled; bool mLevitationEnabled; + /// Called when \a object is moved to an inactive cell + void objectLeftActiveCell (MWWorld::Ptr object, MWWorld::Ptr movedPtr); + public: World (OEngine::Render::OgreRenderer& renderer, @@ -493,7 +502,7 @@ namespace MWWorld virtual void castSpell (const MWWorld::Ptr& actor); - virtual void launchProjectile (const std::string& id, const ESM::EffectList& effects, + virtual void launchProjectile (const std::string& id, bool stack, const ESM::EffectList& effects, const MWWorld::Ptr& actor, const std::string& sourceName); }; } From b82ee4b44f36c40ffe4a92cd2c5904b7c82e4bd5 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 28 Nov 2013 21:49:15 +0100 Subject: [PATCH 369/434] Fix some problems with the previous commit --- apps/openmw/mwworld/worldimp.cpp | 33 +++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index dcf0d7ca99..d6ec6e810c 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2163,27 +2163,38 @@ namespace MWWorld btVector3 from(pos.x, pos.y, pos.z); btVector3 to(newPos.x, newPos.y, newPos.z); std::vector > collisions = mPhysEngine->rayTest2(from, to); - for (std::vector >::iterator cIt = collisions.begin(); cIt != collisions.end(); ++cIt) + bool explode = false; + for (std::vector >::iterator cIt = collisions.begin(); cIt != collisions.end() && !explode; ++cIt) { MWWorld::Ptr obstacle = searchPtrViaHandle(cIt->second); - if (obstacle.isEmpty()) - { - // Terrain. TODO: Explode - continue; - } if (obstacle == ptr) continue; + + explode = true; + MWWorld::Ptr caster = searchPtrViaHandle(it->second.mActorHandle); if (caster.isEmpty()) caster = obstacle; - MWMechanics::CastSpell cast(caster, obstacle); - cast.mStack = it->second.mStack; - cast.mId = it->second.mId; - cast.mSourceName = it->second.mSourceName; - cast.inflict(obstacle, caster, it->second.mEffects, ESM::RT_Target, false); + if (obstacle.isEmpty()) + { + // Terrain + } + else + { + MWMechanics::CastSpell cast(caster, obstacle); + cast.mStack = it->second.mStack; + cast.mId = it->second.mId; + cast.mSourceName = it->second.mSourceName; + cast.inflict(obstacle, caster, it->second.mEffects, ESM::RT_Target, false); + } deleteObject(ptr); mProjectiles.erase(it++); + } + + if (explode) + { + // TODO: Explode continue; } From ffc885853a173e5febac4b5f76429149e08bcf94 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 29 Nov 2013 10:39:37 +0100 Subject: [PATCH 370/434] Fix bolt for magic effects that don't have one specified --- apps/openmw/mwrender/animation.cpp | 1 + apps/openmw/mwworld/worldimp.cpp | 2 ++ 2 files changed, 3 insertions(+) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 15df907b2f..dddbc2c733 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1066,6 +1066,7 @@ void Animation::addEffect(const std::string &model, int effectId, bool loop, con for(size_t i = 0;i < params.mObjects.mParticles.size(); ++i) { Ogre::ParticleSystem* partSys = params.mObjects.mParticles[i]; + sh::Factory::getInstance()._ensureMaterial(partSys->getMaterialName(), "Default"); Ogre::MaterialPtr mat = Ogre::MaterialManager::getSingleton().getByName(partSys->getMaterialName()); static int count = 0; Ogre::String materialName = "openmw/" + Ogre::StringConverter::toString(count++); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index d6ec6e810c..691c7fea47 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2086,6 +2086,8 @@ namespace MWWorld iter->mEffectID); projectileModel = magicEffect->mBolt; + if (projectileModel.empty()) + projectileModel = "VFX_DefaultBolt"; static const std::string schools[] = { "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" From ce2d521b8ffd9d9819cb12f0f4b8542efd1ed0bb Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 29 Nov 2013 20:03:50 +0100 Subject: [PATCH 371/434] Always apply queued movement, even when there's no duration. Fixes crash with --start="bal isra". When a script disables a reference that still has movement queued, trying to apply that movement will then fail due to the reference not being in the scene. Therefore, we should make sure that movement is always applied in the frame that it's queued in. --- apps/openmw/mwworld/worldimp.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 691c7fea47..3d0bcf6d87 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1127,10 +1127,6 @@ namespace MWWorld void World::doPhysics(float duration) { - /* No duration? Shouldn't be any movement, then. */ - if(duration <= 0.0f) - return; - processDoors(duration); moveProjectiles(duration); From bcf61331ab2ddb9fac4217c94498b7615d1d0b41 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 29 Nov 2013 20:06:54 +0100 Subject: [PATCH 372/434] Bring back the option to not grab mouse. Useful if running in a mouse-controlled GUI debugger. --- apps/openmw/engine.cpp | 3 ++- apps/openmw/engine.hpp | 4 ++++ apps/openmw/main.cpp | 4 ++++ apps/openmw/mwinput/inputmanagerimp.cpp | 4 ++-- apps/openmw/mwinput/inputmanagerimp.hpp | 2 +- extern/sdl4ogre/sdlinputwrapper.cpp | 7 ++++--- extern/sdl4ogre/sdlinputwrapper.hpp | 3 ++- 7 files changed, 19 insertions(+), 8 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 4a3c418f6c..a729bdda1e 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -147,6 +147,7 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) , mEncoding(ToUTF8::WINDOWS_1252) , mEncoder(NULL) , mActivationDistanceOverride(-1) + , mGrab(true) { std::srand ( std::time(NULL) ); @@ -370,7 +371,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) std::string keybinderUser = (mCfgMgr.getUserPath() / "input.xml").string(); bool keybinderUserExists = boost::filesystem::exists(keybinderUser); - MWInput::InputManager* input = new MWInput::InputManager (*mOgre, *this, keybinderUser, keybinderUserExists); + MWInput::InputManager* input = new MWInput::InputManager (*mOgre, *this, keybinderUser, keybinderUserExists, mGrab); mEnvironment.setInputManager (input); MWGui::WindowManager* window = new MWGui::WindowManager( diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index 553d290687..e0f8f94e69 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -79,6 +79,8 @@ namespace OMW bool mScriptConsoleMode; std::string mStartupScript; int mActivationDistanceOverride; + // Grab mouse? + bool mGrab; Compiler::Extensions mExtensions; Compiler::Context *mScriptContext; @@ -152,6 +154,8 @@ namespace OMW /// Start as a new game. void setNewGame(bool newGame); + void setGrabMouse(bool grab) { mGrab = grab; } + /// Initialise and enter main loop. void go(); diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index a36b6e12f5..42815e330b 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -154,6 +154,8 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat ("fallback", bpo::value()->default_value(FallbackMap(), "") ->multitoken()->composing(), "fallback values") + ("no-grab", "Don't grab mouse cursor") + ("activate-dist", bpo::value ()->default_value (-1), "activation distance override"); bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv) @@ -184,6 +186,8 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat if (!run) return false; + engine.setGrabMouse(!variables.count("no-grab")); + // Font encoding settings std::string encoding(variables["encoding"].as()); std::cout << ToUTF8::encodingUsingMessage(encoding) << std::endl; diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 6901bcdb92..ab25696351 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -87,7 +87,7 @@ namespace MWInput { InputManager::InputManager(OEngine::Render::OgreRenderer &ogre, OMW::Engine& engine, - const std::string& userFile, bool userFileExists) + const std::string& userFile, bool userFileExists, bool grab) : mOgre(ogre) , mPlayer(NULL) , mEngine(engine) @@ -111,7 +111,7 @@ namespace MWInput Ogre::RenderWindow* window = ogre.getWindow (); - mInputManager = new SFO::InputWrapper(mOgre.getSDLWindow(), mOgre.getWindow()); + mInputManager = new SFO::InputWrapper(mOgre.getSDLWindow(), mOgre.getWindow(), grab); mInputManager->setMouseEventCallback (this); mInputManager->setKeyboardEventCallback (this); mInputManager->setWindowEventCallback(this); diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index e7b7d6c7f5..8efa6cfc5a 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -61,7 +61,7 @@ namespace MWInput public: InputManager(OEngine::Render::OgreRenderer &_ogre, OMW::Engine& engine, - const std::string& userFile, bool userFileExists); + const std::string& userFile, bool userFileExists, bool grab); virtual ~InputManager(); diff --git a/extern/sdl4ogre/sdlinputwrapper.cpp b/extern/sdl4ogre/sdlinputwrapper.cpp index 9990e828aa..d48e43c010 100644 --- a/extern/sdl4ogre/sdlinputwrapper.cpp +++ b/extern/sdl4ogre/sdlinputwrapper.cpp @@ -9,7 +9,7 @@ namespace SFO { /// \brief General purpose wrapper for OGRE applications around SDL's event /// queue, mostly used for handling input-related events. - InputWrapper::InputWrapper(SDL_Window* window, Ogre::RenderWindow* ogreWindow) : + InputWrapper::InputWrapper(SDL_Window* window, Ogre::RenderWindow* ogreWindow, bool grab) : mSDLWindow(window), mOgreWindow(ogreWindow), mWarpCompensate(false), @@ -27,7 +27,8 @@ namespace SFO mWindowHasFocus(true), mWantGrab(false), mWantRelative(false), - mWantMouseVisible(false) + mWantMouseVisible(false), + mAllowGrab(grab) { _setupOISKeys(); } @@ -226,7 +227,7 @@ namespace SFO void InputWrapper::updateMouseSettings() { mGrabPointer = mWantGrab && mMouseInWindow && mWindowHasFocus; - SDL_SetWindowGrab(mSDLWindow, mGrabPointer ? SDL_TRUE : SDL_FALSE); + SDL_SetWindowGrab(mSDLWindow, mGrabPointer && mAllowGrab ? SDL_TRUE : SDL_FALSE); SDL_ShowCursor(mWantMouseVisible || !mWindowHasFocus); diff --git a/extern/sdl4ogre/sdlinputwrapper.hpp b/extern/sdl4ogre/sdlinputwrapper.hpp index ca57464e92..a2b698f860 100644 --- a/extern/sdl4ogre/sdlinputwrapper.hpp +++ b/extern/sdl4ogre/sdlinputwrapper.hpp @@ -16,7 +16,7 @@ namespace SFO class InputWrapper { public: - InputWrapper(SDL_Window *window, Ogre::RenderWindow* ogreWindow); + InputWrapper(SDL_Window *window, Ogre::RenderWindow* ogreWindow, bool grab); ~InputWrapper(); void setMouseEventCallback(MouseListener* listen) { mMouseListener = listen; } @@ -62,6 +62,7 @@ namespace SFO bool mWarpCompensate; bool mWrapPointer; + bool mAllowGrab; bool mWantMouseVisible; bool mWantGrab; bool mWantRelative; From 0e267b79ecec7e085b344a98dec6d6ec85739cd0 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 29 Nov 2013 20:21:57 +0100 Subject: [PATCH 373/434] Don't heal dead actors when resting --- apps/openmw/mwmechanics/actors.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index def5708a62..161c8dcfe4 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -160,6 +160,8 @@ namespace MWMechanics void Actors::calculateRestoration (const MWWorld::Ptr& ptr, float duration) { + if (ptr.getClass().getCreatureStats(ptr).isDead()) + return; CreatureStats& stats = MWWorld::Class::get (ptr).getCreatureStats (ptr); const MWWorld::Store& settings = MWBase::Environment::get().getWorld()->getStore().get(); From bb4bd999ba706e672380a6921af9776c93a5cf58 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 30 Nov 2013 08:29:22 +0100 Subject: [PATCH 374/434] PlaceAt: Copy the rotation when placing a non-actor. Don't modify placement position by bounding box for non-actors. Fixes placement in Graphic Herbalism mod. --- .../openmw/mwscript/transformationextensions.cpp | 16 +++++++++++++--- apps/openmw/mwworld/worldimp.cpp | 15 +++++++++------ 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 4b60f47ce2..ae9ac041e1 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -490,10 +490,20 @@ namespace MWScript ipos.pos[0] = pos.x; ipos.pos[1] = pos.y; ipos.pos[2] = pos.z; - ipos.rot[0] = 0; - ipos.rot[1] = 0; - ipos.rot[2] = 0; + if (actor.getClass().isActor()) + { + // TODO: should this depend on the 'direction' parameter? + ipos.rot[0] = 0; + ipos.rot[1] = 0; + ipos.rot[2] = 0; + } + else + { + ipos.rot[0] = actor.getRefData().getPosition().rot[0]; + ipos.rot[1] = actor.getRefData().getPosition().rot[1]; + ipos.rot[2] = actor.getRefData().getPosition().rot[2]; + } // create item MWWorld::CellStore* store = actor.getCell(); MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), itemID, count); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 3d0bcf6d87..1247ae6e6b 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1543,12 +1543,15 @@ namespace MWWorld MWWorld::Ptr dropped = MWWorld::Class::get(object).copyToCell(object, cell, pos); - Ogre::Vector3 min, max; - if (mPhysics->getObjectAABB(object, min, max)) { - float *pos = dropped.getRefData().getPosition().pos; - pos[0] -= (min.x + max.x) / 2; - pos[1] -= (min.y + max.y) / 2; - pos[2] -= min.z; + if (object.getClass().isActor()) + { + Ogre::Vector3 min, max; + if (mPhysics->getObjectAABB(object, min, max)) { + float *pos = dropped.getRefData().getPosition().pos; + pos[0] -= (min.x + max.x) / 2; + pos[1] -= (min.y + max.y) / 2; + pos[2] -= min.z; + } } if (mWorldScene->isCellActive(cell)) { From 6aa9e189155b1850b0750892a74450dc2ce18b12 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 30 Nov 2013 08:33:18 +0100 Subject: [PATCH 375/434] Reset filter when starting a trade --- apps/openmw/mwgui/tradewindow.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index 636b8ae9b6..d544b83cf4 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -80,7 +80,7 @@ namespace MWGui } void TradeWindow::startTrade(const MWWorld::Ptr& actor) - { + { mPtr = actor; mCurrentBalance = 0; @@ -102,6 +102,8 @@ namespace MWGui // Careful here. setTitle may cause size updates, causing itemview redraw, so make sure to do it last // or we end up using a possibly invalid model. setTitle(MWWorld::Class::get(actor).getName(actor)); + + onFilterChanged(mFilterAll); } void TradeWindow::onFilterChanged(MyGUI::Widget* _sender) From e1e9de0f02ea4d1f750e055829bbc1c86bd96219 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 30 Nov 2013 09:04:52 +0100 Subject: [PATCH 376/434] Don't hide tooltips on mouse click. Probably wasn't such a bright idea. --- apps/openmw/mwgui/tooltips.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 85c71575b6..b52c8e3ddb 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -110,11 +110,6 @@ namespace MWGui else { - const MyGUI::IntPoint& lastPressed = MyGUI::InputManager::getInstance().getLastPressedPosition(MyGUI::MouseButton::Left); - - if (mousePos == lastPressed) // mouseclick makes tooltip disappear - return; - if (mousePos.left == mLastMouseX && mousePos.top == mLastMouseY) { mRemainingDelay -= frameDuration; From 4b4025ed0fb87113700dd1208657350a58fde5e1 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 30 Nov 2013 10:50:02 +0100 Subject: [PATCH 377/434] Keep the player's CharacterController when changing cells. Fixes several glitches. --- apps/openmw/mwmechanics/actors.cpp | 4 ++-- apps/openmw/mwmechanics/actors.hpp | 4 ++-- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 5 +---- apps/openmw/mwworld/scene.cpp | 8 ++++---- apps/openmw/mwworld/worldimp.cpp | 2 +- 5 files changed, 10 insertions(+), 13 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 161c8dcfe4..22a641b34b 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -516,12 +516,12 @@ namespace MWMechanics } } - void Actors::dropActors (const MWWorld::Ptr::CellStore *cellStore) + void Actors::dropActors (const MWWorld::Ptr::CellStore *cellStore, const MWWorld::Ptr& ignore) { PtrControllerMap::iterator iter = mActors.begin(); while(iter != mActors.end()) { - if(iter->first.getCell()==cellStore) + if(iter->first.getCell()==cellStore && iter->first != ignore) { delete iter->second; mActors.erase(iter++); diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index b733c966be..6afdefdbdc 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -68,8 +68,8 @@ namespace MWMechanics void updateActor(const MWWorld::Ptr &old, const MWWorld::Ptr& ptr); ///< Updates an actor with a new Ptr - void dropActors (const MWWorld::CellStore *cellStore); - ///< Deregister all actors in the given cell. + void dropActors (const MWWorld::CellStore *cellStore, const MWWorld::Ptr& ignore); + ///< Deregister all actors (except for \a ignore) in the given cell. void update (float duration, bool paused); ///< Update actor stats and store desired velocity vectors in \a movement diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index ff13841a2f..1316baaeb4 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -200,10 +200,7 @@ namespace MWMechanics void MechanicsManager::drop(const MWWorld::CellStore *cellStore) { - if(!mWatched.isEmpty() && mWatched.getCell() == cellStore) - mWatched = MWWorld::Ptr(); - - mActors.dropActors(cellStore); + mActors.dropActors(cellStore, mWatched); mObjects.dropObjects(cellStore); } diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 254ad98cfb..25ce038f36 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -111,7 +111,9 @@ namespace MWWorld mRendering.removeCell(*iter); MWBase::Environment::get().getWorld()->getLocalScripts().clearCell (*iter); + MWBase::Environment::get().getMechanicsManager()->drop (*iter); + MWBase::Environment::get().getSoundManager()->stopSound (*iter); mActiveCells.erase(*iter); } @@ -164,6 +166,7 @@ namespace MWWorld void Scene::playerCellChange(MWWorld::CellStore *cell, const ESM::Position& pos, bool adjustPlayerPos) { MWBase::World *world = MWBase::Environment::get().getWorld(); + MWWorld::Ptr old = world->getPlayer().getPlayer(); world->getPlayer().setCell(cell); MWWorld::Ptr player = world->getPlayer().getPlayer(); @@ -183,7 +186,7 @@ namespace MWWorld MWBase::MechanicsManager *mechMgr = MWBase::Environment::get().getMechanicsManager(); - mechMgr->add(player); + mechMgr->updateCell(old, player); mechMgr->watchActor(player); MWBase::Environment::get().getWindowManager()->changeCell(mCurrentCell); @@ -205,9 +208,6 @@ namespace MWWorld Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::ScopedLoad load(loadingListener); - // remove active - MWBase::Environment::get().getMechanicsManager()->remove(MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); - std::string loadingExteriorText = "#{sLoadingMessage3}"; loadingListener->setLabel(loadingExteriorText); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 1247ae6e6b..525c811af3 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -889,7 +889,7 @@ namespace MWWorld int cellY = newCell.mCell->getGridY(); mWorldScene->changeCell(cellX, cellY, pos, false); } - addContainerScripts (ptr, &newCell); + addContainerScripts (getPlayer().getPlayer(), &newCell); } else { From 46973e8e8201380b85ca21e62a70e53e8315acf1 Mon Sep 17 00:00:00 2001 From: pvdk Date: Sat, 30 Nov 2013 12:08:37 +0100 Subject: [PATCH 378/434] Fix for Bug #982: unchecking addons is now saved to profile --- apps/launcher/datafilespage.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 734277b97d..d5eb458f80 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -116,8 +116,7 @@ void Launcher::DataFilesPage::buildView() void Launcher::DataFilesPage::removeProfile(const QString &profile) { - mLauncherSettings.remove(QString("Profiles/") + profile + QString("/game")); - mLauncherSettings.remove(QString("Profiles/") + profile + QString("/addon")); + mLauncherSettings.remove(QString("Profiles/") + profile); } QAbstractItemModel *Launcher::DataFilesPage::profilesModel() const From 1512ac11adcc4d1d9567447d9704088d48bba95d Mon Sep 17 00:00:00 2001 From: pvdk Date: Sat, 30 Nov 2013 12:16:57 +0100 Subject: [PATCH 379/434] Fixed the content selector checkboxes appearing as partially checked --- components/contentselector/model/contentmodel.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 9183003295..0d274474c6 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -207,8 +207,11 @@ QVariant ContentSelectorModel::ContentModel::data(const QModelIndex &index, int case Qt::CheckStateRole: { - if (!file->isGameFile()) - return isChecked(file->filePath()); + if (file->isGameFile()) + return QVariant(); + + return mCheckStates[file->filePath()]; + break; } From 8391891a5b7583d9f75a995251005fa7c4912aa7 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 30 Nov 2013 14:25:29 +0100 Subject: [PATCH 380/434] Ignore case for content file extensions --- apps/openmw/mwworld/worldimp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 525c811af3..87a8d7d6f6 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -107,7 +107,7 @@ namespace MWWorld void load(const boost::filesystem::path& filepath, int& index) { - LoadersContainer::iterator it(mLoaders.find(filepath.extension().string())); + LoadersContainer::iterator it(mLoaders.find(Misc::StringUtils::lowerCase(filepath.extension().string()))); if (it != mLoaders.end()) { it->second->load(filepath, index); From e03b8ac39313217d4f36e1f8807a3f0ab8a01563 Mon Sep 17 00:00:00 2001 From: Lukasz Gromanowski Date: Sun, 1 Dec 2013 01:06:03 +0100 Subject: [PATCH 381/434] Added *.out files to gitignore. Signed-off-by: Lukasz Gromanowski --- manual/opencs/.gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/manual/opencs/.gitignore b/manual/opencs/.gitignore index cf62bd6fc6..ce5852a376 100644 --- a/manual/opencs/.gitignore +++ b/manual/opencs/.gitignore @@ -2,4 +2,5 @@ *.aux *.log *.toc -*.pdf \ No newline at end of file +*.pdf +*.out From 7c2afc43f4cac1c55f149e37a33a68edae16e937 Mon Sep 17 00:00:00 2001 From: Lukasz Gromanowski Date: Sun, 1 Dec 2013 01:07:08 +0100 Subject: [PATCH 382/434] Small reformatting. Broked long lines, removed unneeded hard line breaks, added defs for common words. Signed-off-by: Lukasz Gromanowski --- manual/opencs/files_and_directories.tex | 107 ++++++++++---- manual/opencs/filters.tex | 179 ++++++++++++++++++------ manual/opencs/main.tex | 18 ++- manual/opencs/tables.tex | 73 +++++++--- manual/opencs/windows.tex | 47 +++++-- 5 files changed, 313 insertions(+), 111 deletions(-) diff --git a/manual/opencs/files_and_directories.tex b/manual/opencs/files_and_directories.tex index b4af951b39..ce686d56cb 100644 --- a/manual/opencs/files_and_directories.tex +++ b/manual/opencs/files_and_directories.tex @@ -1,74 +1,119 @@ \section{Files and Directories} \subsection{Introduction} -This section of the manual covers usage of files and directories by the OpenCS. Files and directories are file system concepts, and you are probably already familiar with it. We won't try to explain this concepts, we will just focus on Open{CS}. +This section of the manual covers usage of files and directories by the OpenCS. Files and directories are file system concepts, +and you are probably already familiar with it. We won't try to explain this concepts, we will just focus on Open{CS}. \subsection{Used terms} %TODO \subsection{Basics} \paragraph{Directories} -Open{MW} and Open{CS} uses multiple directories on file systems. First of, there is a \textbf{user directory} that holds configuration files and few different folders. The location of the user directory is hard coded for each supported operating system.\\ +Open{MW} and Open{CS} uses multiple directories on file systems. First of, there is a \textbf{user directory} that holds configuration +files and few different folders. The location of the user directory is hard coded for each supported operating system. %TODO list paths. - -In addition to this single hard coded directory, both Open{MW} and Open{CS} need a place to seek for actual data files of the game: textures, models, sounds and files that store records of objects in game; dialogues and so one -- so called content files. We support multiple such paths (We call it \textbf{data paths}) as specified in the configuration. Usually one data path points to the directory where original Morrowind\texttrademark is either installed or unpacked. You are free to specify as many data paths as you would like, however, there is one special data path that, as described later, is used to store newly created content files. +In addition to this single hard coded directory, both Open{MW} and Open{CS} need a place to seek for actual data files of the game: +textures, models, sounds and files that store records of objects in game; dialogues and so one -- so called content files. We support +multiple such paths (We call it \textbf{data paths}) as specified in the configuration. Usually one data path points to the directory +where original Morrowind\texttrademark is either installed or unpacked. You are free to specify as many data paths as you would like, +however, there is one special data path that, as described later, is used to store newly created content files. \paragraph{Content files} -Bethesda Morrowind\texttrademark engine is using two types of files: esm (master) and esp (plugin). The distinction between those is not clear, and often confusing. You would expect the esm (master) file is used to specify one master, that is modified by the esps plugins, and indeed: this is the basic idea. However, original expansions also were made as esm files, even though they essentially could be described as a really large plugins, and therefore rather use esp files. There were technical reasons behind this decision -- somewhat valid in the case of original engine, but clearly it's better to create a system that can be used is more sensible way. Open{MW} achieves this with our own content file types.\\ -We support both esm and esp files, but in order to make use of new features of OpenMW one should consider using new file types designed with our engine in mind: game files and addon files together called ``content files``.\\ +\BS \MW engine is using two types of files: ESM (master) and ESP (plugin). The distinction between those +is not clear, and often confusing. You would expect the ESM (master) file is used to specify one master, that is modified by the ESPs plugins, +and indeed: this is the basic idea. However, original expansions also were made as ESM files, even though they essentially could be +described as a really large plugins, and therefore rather use ESP files. There were technical reasons behind this decision -- somewhat valid +in the case of original engine, but clearly it's better to create a system that can be used is more sensible way. Open{MW} achieves +his with our own content file types. + +We support both ESM and ESP files, but in order to make use of new features of OpenMW one should consider using new file types designed +with our engine in mind: game files and addon files together called ``content files``. \subparagraph{Open{MW} content files} -Game and Addon files are concept somewhat similar to the old esm/esp, only in the way it should be from the very beginning. Nothing easier to describe. If you want to make new game using Open{MW} as engine (so called ``total conversion'') you should create a game file. If you want to create a addon for existing game file -- simply create addon file. Nothing else matters: The only distinction you should consider is if your project is about changing other game, or creating a new one. Simple as that.\\ +Game and Addon files are concept somewhat similar to the old ESM/ESP, only in the way it should be from the very beginning. Nothing easier +to describe. If you want to make new game using Open{MW} as engine (so called ``total conversion'') you should create a game file. +If you want to create a addon for existing game file -- simply create addon file. Nothing else matters: The only distinction you should +consider is if your project is about changing other game, or creating a new one. Simple as that. -Other simple thing about content files are extensions. We are using .omwaddon for addon files and .omwgame for game files.\\ +Other simple thing about content files are extensions. We are using .omwaddon for addon files and .omwgame for game files. %TODO describe what content files contains. and what not. +\subparagraph{\MW content files} +Using our content files is recommended solution for projects that are intended to used with Open{MW} engine. However some players +wish to use original Morrowind engine, even with it large flaws and lacking features\footnote{If this is actually wrong, We are very +successful project. Yay!}. Also, since 2002 thousands of ESP/ESM files were created, some with really outstanding content. +Because of this Open{CS} simply has no other choice but support ESP/ESM files. However, if you decided to choose ESP/ESM file instead +using our own content file types you are most likely aim at the original engine compatibility. This subject is covered in the very +last section of this manual. %not finished TODO add the said section. Most likely when more features are present. -\subparagraph{Morrowind content files} -Using our content files is recommended solution for projects that are intended to used with Open{MW} engine. However some players wish to use original Morrowind engine, even with it large flaws and lacking features\footnote{If this is actually wrong, We are very successful project. Yay!}. Also, since 2002 thousands of esp/ems files were created, some with really outstanding content. Because of this Open{CS} simply has no other choice but support esp/esm files. However, if you decided to choose esp/esm file instead using our own content file types you are most likely aim at the original engine compatibility. This subject is covered in the very last section of this manual.\\ %not finished TODO add the said section. Most likely when more features are present. - -The actual creation of new files is described in the next chapter. Here We are gonna focus only on details that you need to know in order to create your first Open{CS} file while full understanding your needs. For now let's jut remember that content files are created inside the user directory, in the the \textbf{data} subfolder (that is the one special data directory mentioned earlier).\\ +The actual creation of new files is described in the next chapter. Here we are gonna focus only on details that you need to know +in order to create your first Open{CS} file while full understanding your needs. For now let's jut remember that content files +are created inside the user directory, in the the \textbf{data} subfolder (that is the one special data directory mentioned earlier). \subparagraph{Dependencies} -Since addon is supposed to change the game it is logical that it also depends on the said game. It simply can't work otherwise. Just think about it: your modification is changing prize of the iron sword. But what if there is no iron sword in game? That's right: we get nonsense. What you want to do is to tie your addon to the files you are changing. Those can be either game files (expansion island for a game) or other addon files (house on the said island). It is a a good idea to be dependent only on files that are really changed in your addon obviously, but sadly there is no other way to achieve this than knowing what you want to do. Again, please remember that this section of the manual does not cover creating the content files -- it is only theoretical introduction to the subject. For now just keep in mind that dependencies exist, and is up to you what to decide if your content file should depend on other content file.\\ +Since addon is supposed to change the game it is logical that it also depends on the said game. It simply can not work otherwise. +Just think about it: your modification is changing prize of the iron sword. But what if there is no iron sword in game? That is right: +we get nonsense. What you want to do is to tie your addon to the files you are changing. Those can be either game files (expansion island +for a game) or other addon files (house on the said island). It is a good idea to be dependent only on files that are really changed +in your addon obviously, but sadly there is no other way to achieve this than knowing what you want to do. Again, please remember that +this section of the manual does not cover creating the content files -- it is only theoretical introduction to the subject. For now just +keep in mind that dependencies exist, and is up to you what to decide if your content file should depend on other content file. -Game files are not intend to have any dependencies for a very simple reasons: player is using only one game file (excluding original and dirty esp/esm system) at the time and therefore no game file can depend on other game file, and since game file makes the base for addon files -- it can't depend on addon files.\\ - -\subparagraph{Loading order} -%TODO +Game files are not intend to have any dependencies for a very simple reasons: player is using only one game file (excluding original +and dirty ESP/ESM system) at the time and therefore no game file can depend on other game file, and since game file makes the base +for addon files -- it can not depend on addon files. +%\subparagraph{Loading order} %TODO \paragraph{Project files} -Project files act as containers for data not used by the Openm{MW} game engine itself, but still useful for OpenCS. The shining example of this data category are without doubt record filters (described in the later section of the manual you are reading currently). As a mod author you probably don't need and/or want to distribute project files at all, they are meant to be used only by you.\\ -As you would imagine, project file makes sense only in combination with actual content files. In fact, each time you start to work on new content file and project file was not found, it will be created.\\ -Project files extension is, to not surprise ``.project''. The whole name of the project file is the whole name of the content file with appended extensions. For instance swords.omwaddon file is associated with swords.omwaddon.project file.\\ +Project files act as containers for data not used by the Open{MW} game engine itself, but still useful for OpenCS. The shining example +of this data category are without doubt record filters (described in the later section of the manual you are reading currently). +As a mod author you probably do not need and/or want to distribute project files at all, they are meant to be used only by you. + +As you would imagine, project file makes sense only in combination with actual content files. In fact, each time you start to work +on new content file and project file was not found, it will be created. +Project files extension is, to not surprise ``.project''. The whole name of the project file is the whole name of the content file +with appended extensions. For instance swords.omwaddon file is associated with swords.omwaddon.project file. %TODO where are they stored. -Project files are stored inside the user directory, in the \textbf{projects} subfolder. This is the path location for both freshly created project files, and a place where Open{CS} looks for already existing files.\\ +Project files are stored inside the user directory, in the \textbf{projects} subfolder. This is the path location for both freshly +created project files, and a place where Open{CS} looks for already existing files. \paragraph{Resources files} %textures, sounds, whatever -Unless we are talking about the fully text based game, like Zork or Rogue, you are expecting that a video game is using some media files: models with textures, pictures acting as icons, sounds and everything else. Since content files, no matter if it is esp, esm or new Open{MW} file type do not contain any of those, it's clear that they have to be deliver with a different file. It is also clear that this, let's call it ``resources file``, have to be supported by the engine. Without code handling those files, it is nothing more than a mathematical abstraction -- something, that lacks meaning for human beings\footnote{Unless we call programmers a human beings.}. Therefore this section must cover ways to add resources files to your content file, and point out what is supported. We are going to do just that. Later, you will learn how to make use of those files in your content. +Unless we are talking about the fully text based game, like Zork or Rogue, you are expecting that a video game is using some media files: +models with textures, pictures acting as icons, sounds and everything else. Since content files, no matter if it is ESP, ESM or new Open{MW} +file type do not contain any of those, it is clear that they have to be deliver with a different file. It is also clear that this, +let's call it ``resources file``, have to be supported by the engine. Without code handling those files, it is nothing more than +a mathematical abstraction -- something, that lacks meaning for human beings\footnote{Unless we call programmers a human beings.}. +Therefore this section must cover ways to add resources files to your content file, and point out what is supported. We are going +to do just that. Later, you will learn how to make use of those files in your content. \subparagraph{Audio} -Open{MW} is using {FF}mpeg for audio playback, and so we support every audio type that is supported by this library. This makes a huge list. Below is only small portion of supported file types. +Open{MW} is using {FF}mpeg for audio playback, and so we support every audio type that is supported by this library. This makes a huge list. +Below is only small portion of supported file types. \begin{description} - \item mp3 popular format and \textit{de facto} standard for storing audio. Used by the Morrowind game. - \item mp4 format with a better compression rate than mp3, but also requiring more {CPU} intensive decoding -- this makes it probably less suited for video games. - \item ogg open source, audio container file using high quality vorbis codec. Recommended. + \item mp3 (MPEG-1 Part 3 Layer 3) popular audio file format and \textit{de facto} standard for storing audio. Used by the Morrowind game. + \item ogg open source, multimedia container file using high quality vorbis audio codec. Recommended. \end{description} - \subparagraph{Video} -As in the case of audio files, we are using {FFmepg} to decode video files. The list of supported files is long, we will cover only the most significant. +As in the case of audio files, we are using {FFmepg} to decode video files. The list of supported files is long, we will cover +only the most significant. \begin{description} \item bik videos used by original Morrowind game. - \item webm is a new, shiny and open source video format with excellent compression. It needs quite a lot of processing power to be decoded, but since game logic is not running during cut scenes we can recommended it for use with Open{MW}. + \item mp4 multimedia container which use more advanced codecs (MPEG-4 Parts 2,3,10) with a better audio and video compression rate, + but also requiring more {CPU} intensive decoding -- this makes it probably less suited for video games. + \item webm is a new, shiny and open source video format with excellent compression. It needs quite a lot of processing power to be decoded, + but since game logic is not running during cut scenes we can recommended it for use with Open{MW}. \item ogv alternative, open source container using theora codec for video and vorbis for audio. \end{description} \subparagraph{Textures and images} -Original Morrowind game uses dds and tga file for all kind of two dimensional images and textures alike. In addition, engine supported bmp files for some reason (bmp is a terrible format for a video game). We also support extended set of image files -- including jpeg and png. Jpeg and png files can be useful in some cases, for instance jpeg file is a valid option for skybox texture and png can useful for masks. However please, keep in mind that jpeg can grow into large sizes quickly and are not the best option with directx rendering backend. +Original \MW game uses DDS and TGA files for all kind of two dimensional images and textures alike. In addition, engine supported BMP +files for some reason (BMP is a terrible format for a video game). We also support extended set of image files -- including JPEG and PNG. +JPEG and PNG files can be useful in some cases, for instance JPEG file is a valid option for skybox texture and PNG can useful for masks. +However please, keep in mind that JPEG can grow into large sizes quickly and are not the best option with DirectX rendering backend. -\subparagraph{Meshes} %TODO once we will support something more than just nifs \ No newline at end of file +%\subparagraph{Meshes} %TODO once we will support something more than just nifs \ No newline at end of file diff --git a/manual/opencs/filters.tex b/manual/opencs/filters.tex index cf0fc72026..e3781619c9 100644 --- a/manual/opencs/filters.tex +++ b/manual/opencs/filters.tex @@ -1,25 +1,38 @@ \section{Record filters} \subsection{Introduction} -Filters are the key element of OpenCS use cases by allowing rapid and easy access to the searched records presented in all tables. Therefore: in order to use this application fully effective you should make sure that all concepts and instructions written in the this section of the manual are perfectly clear to you.\\ +Filters are the key element of OpenCS use cases by allowing rapid and easy access to the searched records presented in all tables. +Therefore: in order to use this application fully effective you should make sure that all concepts and instructions written in +the this section of the manual are perfectly clear to you. + Don't be afraid though, filters are fairly intuitive and easy to use. \subsubsection{Used Terms} \begin{description} - \item[Filter] is generally speaking a tool able to ``Filter'' (that is: select some elements, while discarding others) according to the some criteria. In case of OpenCS: records are being filtered according to the criteria of user choice. Criteria are written down in language with simple syntax. + \item[Filter] is generally speaking a tool able to ``Filter'' (that is: select some elements, while discarding others) according + to the some criteria. In case of OpenCS: records are being filtered according to the criteria of user choice. Criteria are written + down in language with simple syntax. \item[Criteria] describes condition under with any any record is being select by the filter. - \item[Syntax] as you may noticed computers (in general) are rather strict, and expect only strictly formulated orders -- that is: written with correct syntax. - \item[Expression] is way we are actually performing filtering. Filter can be treated as ``functions'': accepts arguments, and evaluates either to the true or false for every column record at the time. - \item[N-ary] is any expression that expects one or more expressions as arguments. It is useful for grouping two (or more) other expressions together in order to create filter that will check for criteria placed in two (again: or more) columns (logical ``or'', ``and''). - \item[unary] is any expression that expects one other expression. The example is ``not'' expression. In fact ``not'' is the only useful unary expression in OpenCS record filters. + \item[Syntax] as you may noticed computers (in general) are rather strict, and expect only strictly formulated orders -- that is: + written with correct syntax. + \item[Expression] is way we are actually performing filtering. Filter can be treated as ``functions'': accepts arguments, and evaluates + either to the true or false for every column record at the time. + \item[N-ary] is any expression that expects one or more expressions as arguments. It is useful for grouping two (or more) other expressions + together in order to create filter that will check for criteria placed in two (again: or more) columns (logical ``or'', ``and''). + \item[unary] is any expression that expects one other expression. The example is ``not'' expression. In fact ``not'' is the only useful + unary expression in OpenCS record filters. \item[nullary] is expression that does not accepts other expressions. It accepts arguments specified later. \end{description} \subsubsection{Basics} -In fact you don't need to learn everything about filters in order to use them. In fact all you need to know to achieve decent productivity with OpenCS is inside basics section. +In fact you don't need to learn everything about filters in order to use them. In fact all you need to know to achieve decent productivity +with OpenCS is inside basics section. \subsubsection{Interface} -Above each table there is a field that is used to enter filter: either predefined by the OpenMW developers or made by you, the user. You probably noticed it before. However there is also completely new element, although using familiar table layout. Go to the application menu view, and click filters. You should see set of default filters, made by the OpenMW team in the table with the following columns: filter, description and modified. +Above each table there is a field that is used to enter filter: either predefined by the OpenMW developers or made by you, the user. +You probably noticed it before. However there is also completely new element, although using familiar table layout. Go to the application +menu view, and click filters. You should see set of default filters, made by the OpenMW team in the table with the following columns: filter, +description and modified. \begin{description} \item[ID] contains the name of the filter. @@ -30,87 +43,161 @@ Above each table there is a field that is used to enter filter: either predefine So let's learn how to actually use those to speed up your work. \subsubsection{Using predefined filters} -Using those filters is quite easy and involves typing inside the filter field above the table. For instance, try to open referencables table and type in the filters field the following: ``project::weapons''. As soon as you complete the text, table will magicly alter and will show only the weapons. As you could noticed project::weapons is nothing else than a ID of one of the predefined filters. That's it: in order to use the filter inside the table you simply type it's name inside the filter field.\\ +Using those filters is quite easy and involves typing inside the filter field above the table. For instance, try to open referencables +table and type in the filters field the following: ``project::weapons''. As soon as you complete the text, table will magicly alter +and will show only the weapons. As you could noticed project::weapons is nothing else than a ID of one of the predefined filters. That is it: +in order to use the filter inside the table you simply type it is name inside the filter field. + To make life easier filter IDs follow simple convention. \begin{itemize} - \item Filter ID filtering a specific record type contains usually the name of a specific group. For instance project::weapons filter contains the word weapons (did you noticed?). Plural form is always used. - \item When filtering specific subgroup the ID starts just like in the case of general filter. For instance project::weaponssilver will filter only silver weapons (new mechanic introduced by the Bloodmoon, silver weapons deal double damage against werewolfs) and project::weaponsmagical will filter only magical weapons (able to hurt ghosts and other supernatural creatures). - \item There are few exceptions from the above rule. For instance there is a project::added, project::removed, project::modyfied, project::base. You would probably except something more like ``project::statusadded'' but in this case typing this few extra characters would only help to break your keyboard faster. + \item Filter ID filtering a specific record type contains usually the name of a specific group. For instance project::weapons filter + contains the word weapons (did you noticed?). Plural form is always used. + \item When filtering specific subgroup the ID starts just like in the case of general filter. For instance project::weaponssilver will + filter only silver weapons (new mechanic introduced by the Bloodmoon, silver weapons deal double damage against werewolfs) and + project::weaponsmagical will filter only magical weapons (able to hurt ghosts and other supernatural creatures). + \item There are few exceptions from the above rule. For instance there is a project::added, project::removed, project::modyfied, project::base. + You would probably except something more like ``project::statusadded'' but in this case typing this few extra characters would only + help to break your keyboard faster. \end{itemize} We strongly recommend to take a look at the filters table right now to see what you can filter with that. And try using it! It is very simple. \subsection{Advanced} -Back to the manual? Great.\\ -If you want to create your own filter you have to know exactly what do you want to get in order to translate this into the expressions. Finally, you will have to write this with correct syntax. As a result table will show only desired rows.\\ +Back to the manual? Great. + +If you want to create your own filter you have to know exactly what do you want to get in order to translate this into the expressions. +Finally, you will have to write this with correct syntax. As a result table will show only desired rows. + Advance subsection covers everything that you need to know in order to create any filter you may want to %TODO the filter part is actually wrong \subsubsection{Namespaces} -Did you noticed that every default filter has ``project::`` prefix? It is a ``namespace``, a term borrowed from the C++ language. In case of OpenCS namespace always means scope of the said object\footnote{You are not supposed to understand this at the moment.}. But what does it mean in case of filters? Well, short explanation is actually simple. +Did you noticed that every default filter has ``project::`` prefix? It is a ``namespace``, a term borrowed from the \CPP{} language. +In case of OpenCS namespace always means scope of the said object\footnote{You are not supposed to understand this at the moment.}. +But what does it mean in case of filters? Well, short explanation is actually simple. \begin{description} - \item[project::] namespace indicates that filter is used with the project, in multiple sessions. You can restart Open{CS} and filter is still there. - \item[session::] namespace indicates that filter is not stored trough multiple sessions and once you will quit Open{CS} (close session) the filter will be gone. Forever! Until then it can be found inside the filters table. + \item[project::] namespace indicates that filter is used with the project, in multiple sessions. You can restart Open{CS} and filter + is still there. + \item[session::] namespace indicates that filter is not stored trough multiple sessions and once you will quit Open{CS} (close session) + the filter will be gone. Forever! Until then it can be found inside the filters table. \end{description} -In addition to this two scopes, there is a third one; called one-shot. One-shot filters are not stored (even during single session) anywhere and as the name implies they are supposed to be created when needed only once. Good thing about the one-shot filters is that you don't need to open filters table in order to create it. Instead you just type it directly inside the filter field, starting with ``!''.\\ -Still, you may wonder how you are supposed to write expressions, what expressions you should use, and what syntax looks like. Let's start with nullary expressions that will allow you to create a basic filter. +In addition to this two scopes, there is a third one; called one-shot. One-shot filters are not stored (even during single session) +anywhere and as the name implies they are supposed to be created when needed only once. Good thing about the one-shot filters is that +you do not need to open filters table in order to create it. Instead you just type it directly inside the filter field, starting with +exclamation mark: ``!''. + +Still, you may wonder how you are supposed to write expressions, what expressions you should use, and what syntax looks like. Let's start +with nullary expressions that will allow you to create a basic filter. \subsubsection{Nullary expressions} -All nullary expressions are used in similar manner. First off: you have to write it's name (for instance: ``string'') and secondly: condition that will be checked inside brackets (for instance string(something, something)). If conditions of your expression will be meet by a record (technical speaking: expression will evaluate to true) the record will show up in the table.\\ -It is clear that you need to know what are you checking, that's is: what column of the table contains information that you are interested in and what should be inside specific cell inside this column to meet your requirements. In most cases first word inside brackets sets column you want to see, while the second one sets desired value inside of the cell. To separate column argument from the value argument use comma. +All nullary expressions are used in similar manner. First off: you have to write it is name (for instance: ``string'') and secondly: +condition that will be checked inside brackets (for instance string(something, something)). If conditions of your expression will be meet +by a record (technical speaking: expression will evaluate to true) the record will show up in the table. + +It is clear that you need to know what are you checking, that is: what column of the table contains information that you are interested +in and what should be inside specific cell inside this column to meet your requirements. In most cases first word inside brackets sets column +you want to see, while the second one sets desired value inside of the cell. To separate column argument from the value argument use comma. \paragraph{String -- string(``column'', ``value'')} -String in programmers language is often\footnote{Often, not always. There are different programming languages using slightly different terms.} just a word for anything composed of characters. In case of OpenCS this is in fact true for every value inside the column that is not composed of the pure numbers. Even columns containing only ``true`` and ``false`` values can be targeted by the string expression.\footnote{There is no Boolean (''true'' or ``false'') value in the OpenCS. You should use string for those.} String evaluates to true, when record contains in the specified column exactly the same value as specified. -\\ +String in programmers language is often\footnote{Often, not always. There are different programming languages using slightly different terms.} +just a word for anything composed of characters. In case of OpenCS this is in fact true for every value inside the column that is not composed +of the pure numbers. Even columns containing only ``true`` and ``false`` values can be targeted by the string expression. +\footnote{There is no Boolean (''true'' or ``false'') value in the OpenCS. You should use string for those.} String evaluates to true, +when record contains in the specified column exactly the same value as specified. + Since majority of the columns contain string values, string is among the most often used expressions. Examples: \begin{itemize} - \item string(``Record Type'', ``Weapon'') -- will evaluate to true for all records containing ``Weapon'' in the ``Record Type'' column cell. This group contains every weapon (including arrows and bolts) found in the game. - \item string(``Portable'', ``true'') -- will evaluate to true for all records containing word true inside ``Portable'' column cell. This group contains every portable light sources (lanterns, torches etc.). + \item string(``Record Type'', ``Weapon'') -- will evaluate to true for all records containing ``Weapon'' in the ``Record Type'' column cell. + This group contains every weapon (including arrows and bolts) found in the game. + \item string(``Portable'', ``true'') -- will evaluate to true for all records containing word true inside ``Portable'' column cell. + This group contains every portable light sources (lanterns, torches etc.). \end{itemize} -This is probably enough to create around 90\% string filters you will eventually need. However, this expression is even more powerful -- it accepts regular expressions (also called regexps). Regular expressions is a way to create string criteria that will be matched by one than just one specific value in the column. For instance, you can display both left and right gauntlets with the following expression: ``string("armor type", ".* gauntlet"))`` because ''.*'' in regexps means just: ``anything''. This filter says: please, show me ``any'' gauntlet. There are left and right gauntlets in the morrowind so this will evaluate to true for both. Simple, isn't it? -\\ -Creating regexps can be a difficult and annoying -- especially when you need complex criteria. On the other hand, We are under impression that in reality complex expressions are needed only in sporadic cases. In fact, the truth is: that most of the time only already mentioned ``.*'' is needed and therefore the following description of regexps can be skipped by vast majority of readers.\\ +This is probably enough to create around 90 string filters you will eventually need. However, this expression is even more powerful +-- it accepts regular expressions (also called regexps). Regular expressions is a way to create string criteria that will be matched +by one than just one specific value in the column. For instance, you can display both left and right gauntlets with the following expression: +``string("armor type", ".* gauntlet"))`` because ''.*'' in regexps means just: ``anything''. This filter says: please, show me ``any'' gauntlet. +There are left and right gauntlets in the morrowind so this will evaluate to true for both. Simple, isn't it? -Before working with Regular Expressions, you should understand what actually are regular expressions. Essentially, the idea is simple: when you are writing any word, you are using strictly defined letters -- that is: letters create a word. What you want to do with regular expression is to use set of rules that will match to many words. It is not that difficult to see what it's needed to do so: first, you will clearly need way to determinate what letters you want to match (word is composed by letters).\\ +Creating regexps can be a difficult and annoying -- especially when you need complex criteria. On the other hand, We are under impression +that in reality complex expressions are needed only in sporadic cases. In fact, the truth is: that most of the time only already mentioned +``.*'' is needed and therefore the following description of regexps can be skipped by vast majority of readers. -Before introducing other ways to choose between characters, I want explain anchors. Anchors allows you to decide where to ``look'' in the string. You surely should know about ``\^'' anchor and ``\textdollar''. Putting ``\^`` will tell to Open{CS} to look on the beginning of string, while ''\textdollar`` is used to mark the end of it. For instance, pattern ''\^Pink.* elephant.\textdollar`` Will match any sentence Beginning with the word ''Pink`` and ending with '' elephant.``. Pink fat elephant. Pink cute elephant. It does not matter what is in between, because ''.*`` is used.\\ +Before working with Regular Expressions, you should understand what actually are regular expressions. Essentially, the idea is simple: +when you are writing any word, you are using strictly defined letters -- that is: letters create a word. What you want to do with regular +expression is to use set of rules that will match to many words. It is not that difficult to see what it's needed to do so: first, +you will clearly need way to determinate what letters you want to match (word is composed by letters). -You have already seen the power of the simple ``.*''. But what if you want to chose between only two (or more) letters? Well, this is when ``[|]'' comes in handy. If you write something like: ``\^[a|k].*'' you are simply telling Open{CS} to filter anything that starts with either ``a'' or ``k''. Using ``\^[a|k|l].*'' will work in the same manner, but it will also cover strings starting with ``l''.\\ +Before introducing other ways to choose between characters, I want explain anchors. Anchors allows you to decide where to ``look'' in the string. +You surely should know about ``\^'' anchor and ``\textdollar''. Putting ``\^`` will tell to Open{CS} to look on the beginning of string, +while ''\textdollar`` is used to mark the end of it. For instance, pattern ''\^Pink.* elephant.\textdollar`` Will match any sentence beginning +with the word ''Pink`` and ending with '' elephant.``. Pink fat elephant. Pink cute elephant. It does not matter what is in between, +because ''.*`` is used.\\ -And What if you want to match more than just one latter? Just use ``(|)``. it is pretty similar to the above one letter as you see, but it is used to fit more than just one character. For instance: ''\^(Pink|Green).* (elephant|crocodile).\textdollar`` will be true for all sentences starting with ''Pink`` or ''Green`` and ending with either ''elephant.`` or ''crocodile.``.\\ +You have already seen the power of the simple ``.*''. But what if you want to chose between only two (or more) letters? Well, this is when +``[|]'' comes in handy. If you write something like: ``\^[a|k].*'' you are simply telling Open{CS} to filter anything that starts with either +``a'' or ``k''. Using ``\^[a|k|l].*'' will work in the same manner, but it will also cover strings starting with ``l''. -Regular expressions are not the main topic of this manual. If you wish to learn more on this subject please, read the documentation on Qt regular expressions syntax, or TRE regexp syntax (it is almost like in Qt). Above is just enough to use Open{CS} effectively to be sure.\\ +What if you want to match more than just one latter? Just use ``(|)``. it is pretty similar to the above one letter as you see, but it is +used to fit more than just one character. For instance: ''\^(Pink|Green).* (elephant|crocodile).\textdollar`` will be true for all sentences +starting with ''Pink`` or ''Green`` and ending with either ''elephant.`` or ''crocodile.``. + +Regular expressions are not the main topic of this manual. If you wish to learn more on this subject please, read the documentation on +Qt regular expressions syntax, or TRE regexp syntax (it is almost like in Qt). Above is just enough to use Open{CS} effectively to be sure. \paragraph{Value -- value(``value'', (``open'', ``close''))} -While string expression covers vast group of columns containing string values, there are in fact columns with just numerical values like ``weight``. To filter those we need a value expression. This one works in similar manner to the string filter: first token name and criteria inside brackets. Clearly, conditions should hold column to test in. However in this case wanted value is specified as a range.\\ +While string expression covers vast group of columns containing string values, there are in fact columns with just numerical values like +``weight``. To filter those we need a value expression. This one works in similar manner to the string filter: first token name and criteria +inside brackets. Clearly, conditions should hold column to test in. However in this case wanted value is specified as a range. As you would imagine the range can be specified as including a border value, or excluding. We are using two types of brackets for this: \begin{itemize} \item To include value use [] brackets. For value equal 5, expression value(something, [5, 10]) will evaluate to true. \item To exclude value use () brackets. For value equal 5, expression value(something, (5, 10)) will evaluate to false. - \item Mixing brackets is completely legal. For value equal 10, expression value(something, [5, 10) will evaluate to true. The same expression will evaluate to false for value equal 10. + \item Mixing brackets is completely legal. For value equal 10, expression value(something, [5, 10) will evaluate to true. The same expression + will evaluate to false for value equal 10. \end{itemize} \paragraph{''true`` and ''false``} -Nullary ''true`` and ''false`` do not accept any arguments, and always evaluates to true (in case of ''true``) and false (in case of ''false``) no matter what. The main usage of this expressions is the give users ability to quickly disable some part of the filter that makes heavy use of the logical expressions. +Nullary \textit{true} and \textit{false} do not accept any arguments, and always evaluates to true (in case of \textit{true}) +and false (in case of \textit{false}) no matter what. The main usage of this expressions is the give users ability to quickly +disable some part of the filter that makes heavy use of the logical expressions. \subsubsection{Logical expressions} -This subsection takes care of two remaining groups of expressions: binary and unary. The only unary expression present in the OpenCS is logical not, while the remaining binary expressions are: or, and. This clearly makes them (from the user point of view) belonging to the same group of logical expressions. +This subsection takes care of two remaining groups of expressions: binary and unary. The only unary expression present in the OpenCS is logical +\textit{not}, while the remaining binary expressions are: \textit{or}, \textit{and}. This clearly makes them (from the user point of view) +belonging to the same group of logical expressions. \paragraph{not -- not expression()} -Sometimes you may be in need of reversing the output of the expression. This is where not comes in handy. Adding not before expression will revert it: if expression was returning true, it will return false; if it was returning false, it will return true. Brackets are not needed: not will revert only the first expression following it.\\ -To show this on know example, let's consider the ''string("armor type", ".* gauntlet"))`` filter. As We mentioned earlier this will return true for every gauntlet found in game. In order to show everything, but gauntlets we simply do ''not string("armor type", ".* gauntlet"))``. This is probably not the most useful filter on earth, but this is not a surprise: real value of not expression shines when combined with or, and filter. +Sometimes you may be in need of reversing the output of the expression. This is where \textit{not} comes in handy. Adding \textit{not} before +expression will revert it: if expression was returning true, it will return false; if it was returning false, it will return true. Brackets +are not needed: \textit{not} will revert only the first expression following it. + +To show this on know example, let's consider the ''string("armor type", ".* gauntlet"))`` filter. As we mentioned earlier this will return true +for every gauntlet found in game. In order to show everything, but gauntlets we simply do ''not string("armor type", ".* gauntlet"))``. +This is probably not the most useful filter on earth, but this is not a surprise: real value of \textit{not} expression shines when combined with +\textit{or}, \textit{and} filters. \paragraph{or -- or(expression1(), expression2())} -Or is a expression that will return true if one of the arguments evaluates to true. You can use two or more arguments, separated by the comma.\\ -Or expression is useful when showing two different group of records is needed. For instance the standard actor filter is using the following ''or(string(``record type'', npc), string(``record type'', creature))`` and will show both npcs and creatures. +\textit{Or} is a expression that will return true if one of the arguments evaluates to true. You can use two or more arguments, separated by the comma. + +\textit{Or} expression is useful when showing two different group of records is needed. For instance the standard actor filter is using the following +''or(string(``record type'', npc), string(``record type'', creature))`` and will show both npcs and creatures. \paragraph{and -- and(expression1(), expression2())} -And is a expression that will return true if all arguments evaluates to true. As in the case of ''or`` you can use two or more arguments, separated by a comma.\\ -As We mentioned earlier in the ''not`` filter, combining ''not`` with ''and`` can be very useful. For instance to show all armor types, excluding gauntlets you can write the following: ''and (not string("armor type", ".* gauntlet"), string(''Record Type``, ''Armor``))''. +\textit{And} is a expression that will return true if all arguments evaluates to true. As in the case of ''or`` you can use two or more arguments, +separated by a comma. +As we mentioned earlier in the \textit{not} filter, combining \textit{not} with \textit{and} can be very useful. For instance to show all armor types, +excluding gauntlets you can write the following: ''and (not string("armor type", ".* gauntlet"), string(''Record Type``, ''Armor``))''. \subsubsection{Creating and saving filter} -In order to create and save new filter, you should go to the filters table, right click and select option ``add record'' from the context menu. A horizontal widget group at the bottom of the table should show up. From there you should select a namespace responsible for scope of the filter (described earlier) and desired ID of the filter. After pressing ok button new entry will show up in the filters table. This filter does nothing at the moment, since it still lacks expressions. In order to add your formula simply double click the filter cell of the new entry and write it down there.\\ +In order to create and save new filter, you should go to the filters table, right click and select option ``add record'' from the context menu. +A horizontal widget group at the bottom of the table should show up. From there you should select a namespace responsible for scope of +the filter (described earlier) and desired ID of the filter. After pressing OK button new entry will show up in the filters table. This filter +does nothing at the moment, since it still lacks expressions. In order to add your formula simply double click the filter cell of the new entry +and write it down there. Done! You are free to use your filter. \subsubsection{Replacing the default filters set} -{OpenCS} allows you to substitute default filters set provided by us, with your own filters. In order to do so you should create a new project, add desired filters, remove undesired and save. Rename the file to the ``defaultfilters'' (don't forget to remove .omwaddon.project extension) and place it inside your configuration directory.\\ -The file acts as template for all new project files from now. If you wish to go back to the old default set, simply rename or remove the custom file. \ No newline at end of file +{OpenCS} allows you to substitute default filters set provided by us, with your own filters. In order to do so you should create a new project, +add desired filters, remove undesired and save. Rename the file to the ``defaultfilters'' (do not forget to remove .omwaddon.project extension) +and place it inside your configuration directory. + +The file acts as template for all new project files from now. If you wish to go back to the old default set, simply rename or remove the custom file. diff --git a/manual/opencs/main.tex b/manual/opencs/main.tex index 577b7078b3..3a395e277d 100644 --- a/manual/opencs/main.tex +++ b/manual/opencs/main.tex @@ -1,8 +1,24 @@ \documentclass[american]{article} \usepackage[T1]{fontenc} \usepackage{babel} -\usepackage{graphicx} +\usepackage{txfonts} % Public Times New Roman text & math font +\usepackage[pdftex]{graphicx} +\usepackage[colorlinks,pdftex]{hyperref} \author{OpenMW Team} + +\def\pdfBorderAttrs{/Border [0 0 0] } % No border arround Links + +\def\CPP{{C\kern-.05em\raise.23ex\hbox{+\kern-.05em+}}} +\hypersetup{% + pdfauthor={Copyright \textcopyright{} OpenMW Team }, + pdftitle={OpenCS user manual} +} + +\def\MW{\textit{Morrowind\texttrademark{}\ }} +\def\TB{\textit{Tribunal\ }} +\def\BM{\textit{Bloodmon\ }} +\def\BS{Bethesda Softworks\ } + \begin{document} \title{OpenCS User Manual} diff --git a/manual/opencs/tables.tex b/manual/opencs/tables.tex index fb6d41ba8d..cbb59b2dd7 100644 --- a/manual/opencs/tables.tex +++ b/manual/opencs/tables.tex @@ -1,7 +1,11 @@ \section{Tables} \subsection{Introduction} -If you have launched OpenCS already and played around with it for a bit, you have probably gotten the impression that it contains lots of tables. You'd be spot on: OpenCS is built around using tables. This doesn't mean it works just like Excel or Calc, though. Due to the vast amounts of information involved with Morrowind, tables just made the most sense. You have to be able to spot information quickly and be able to change them on the fly. Let's browse through the various screens and see what all these tables show. +If you have launched OpenCS already and played around with it for a bit, you have probably gotten the impression that it contains lots of tables. +You'd be spot on: OpenCS is built around using tables. This does not mean it works just like Microsoft Excel or Libre Office Calc, though. +Due to the vast amounts of information involved with \MW, tables just made the most sense. You have to be able to spot information quickly +and be able to change them on the fly. +Let's browse through the various screens and see what all these tables show. \subsection{Used Terms} @@ -10,62 +14,87 @@ If you have launched OpenCS already and played around with it for a bit, you hav \begin{description} \item[Record:] An entry in OpenCS representing an item, location, sound, NPC or anything else. - \item[Reference, Referenceable:] When an item is placed in the world, it doesn't create a new record each time. For example, the game world might contain a lot of exquisite belts on different NPCs and in many crates, but they all refer to one specific record: the Exquisite Belt record. In this case, all those belts in crates and on NPCs are references. The central Exquisite Belt record is called a referenceable. This allows modders to make changes to all items of the same type. For example, if you want all exquisite belts to have 4000 enchantment points rather than 400, you will only need to change the referenceable Exquisite Belt rather than all exquisite belts references individually. + \item[Reference, Referenceable:] When an item is placed in the world, it does not create a new record each time. For example, the game world might + contain a lot of exquisite belts on different NPCs and in many crates, but they all refer to one specific record: the Exquisite Belt record. + In this case, all those belts in crates and on NPCs are references. The central Exquisite Belt record is called a referenceable. This allows modders + to make changes to all items of the same type. For example, if you want all exquisite belts to have 4000 enchantment points rather than 400, you will + only need to change the referenceable Exquisite Belt rather than all exquisite belts references individually. \end{description} \subsubsection{Recurring Terms} - Some columns are recurring throughout OpenCS. They show up in (nearly) every table in OpenCS. \begin{description} -\item[ID] Each item, location, sound, etc. gets the same unique identifier in both OpenCS and Morrowind. This is usually a very self-explanatory name. For example, the ID for the (unique) black pants of Caius Cosades is ``Caius\_pants''. This allows you to manipulate the game in many ways. For example, you could add these pants to your inventory by simply opening the console and write: ``player->addItem Caius\_pants''. Either way, in both Morrowind and OpenCS, the ID is the primary way to identify all these different parts of the game. %Wrong! Cells do not have ID, only name. +\item[ID] Each item, location, sound, etc. gets the same unique identifier in both OpenCS and Morrowind. This is usually a very self-explanatory name. +For example, the ID for the (unique) black pants of Caius Cosades is ``Caius\_pants''. This allows you to manipulate the game in many ways. For example, +you could add these pants to your inventory by simply opening the console and write: ``player->addItem Caius\_pants''. Either way, in both Morrowind +and OpenCS, the ID is the primary way to identify all these different parts of the game. %Wrong! Cells do not have ID, only name. \item[Modified] This column shows what has happened (if something has happened) to this record. There are four possible states in which it can exist. -\item[Base] means that this record is part of the base game and is in its original state. Usually, if you create a mod, the base game is Morrowind with optionally the Bloodmoon and Tribunal expansions. +\item[Base] means that this record is part of the base game and is in its original state. Usually, if you create a mod, the base game is Morrowind with +optionally the Bloodmoon and Tribunal expansions. \item[Added] means that this record was not in the base game and has been added by a modder. \item[Modified] means that the record is part of the base game, but has been changed in some way. -\item[Deleted] means that this record used to be part of the base game, but has been removed as an entry. This does not mean, however, that the occurrences in the game itself have been removed! For example, if you remove the CharGen\_Bed entry from morrowind.esm, it doesn't mean the bedroll in the basement of the Census and Excise Office in Seyda Neen is gone. You're going to have to delete that reference yourself or make sure that that object is replaced by something that still exists otherwise you'll get crashes in the worst case scenario. - \end{description} +\item[Deleted] means that this record used to be part of the base game, but has been removed as an entry. This does not mean, however, that the occurrences +in the game itself have been removed! For example, if you remove the CharGen\_Bed entry from morrowind.esm, it does not mean the bedroll in the basement +of the Census and Excise Office in Seyda Neen is gone. You're going to have to delete that reference yourself or make sure that that object is replaced +by something that still exists otherwise you will get crashes in the worst case scenario. +\end{description} \subsection{World Screens} - The contents of the game world can be changed by choosing one of the options in the appropriate menu at the top of the screen. \subsubsection{Regions} - This describes the general areas of Vvardenfell. Each of these areas has different rules about things such as encounters and weather. \begin{description} \item[Name:] This is how the game will show your location in-game. - \item[Map Colour:] This is a six-digit hexidecimal representation of the colour used to identify the region on the map available in World > Region Map. If you don't have an application with a colour picker, you can use your favourite search engine to find a colour picker online. + \item[Map Colour:] This is a six-digit hexidecimal representation of the colour used to identify the region on the map available in + World > Region Map. If you don't have an application with a colour picker, you can use your favourite search engine to find a colour picker online. \item[Sleep Encounter:] These are the rules for what kind of enemies you might encounter when you sleep outside in the wild. \end{description} \subsubsection{Cells} +Expansive worlds such as Vvardenfell, with all its items, NPCs, etc. have a lot going on simultaneously. But if you are in Balmora, +why would the computer need to keep track the exact locations of NPCs walking through the corridors in a Vivec canton? All that work would +be quite useless and bring your system to its knees! So the world has been divided up into squares we call "cells". Once your character enters a cell, +the game will load everything that is going on in that cell so you can interact with it. -Expansive worlds such as Vvardenfell, with all its items, NPCs, etc. have a lot going on simultaneously. But if you are in Balmora, why would the computer need to keep track the exact locations of NPCs walking through the corridors in a Vivec canton? All that work would be quite useless and bring your system to its knees! So the world has been divided up into squares we call "cells". Once your character enters a cell, the game will load everything that is going on in that cell so you can interact with it. - -In the original Morrowind this could be seen when you were travelling and you would see a small loading bar at the bottom of the screen; you had just entered a new cell and the game would have to load all the items and NPCs. The Cells screen in OpenCS provides you with a list of cells in the game, both the interior cells (houses, dungeons, mines, etc.) and the exterior cells (the outside world). +In the original \MW this could be seen when you were travelling and you would see a small loading bar at the bottom of the screen; +you had just entered a new cell and the game would have to load all the items and NPCs. The Cells screen in OpenCS provides you with a list of cells +in the game, both the interior cells (houses, dungeons, mines, etc.) and the exterior cells (the outside world). \begin{description} - \item[Sleep Forbidden:] Can the player sleep on the floor? In most cities it is forbidden to sleep outside. Sleeping in the wild carries its own risks of attack, though, and this entry lets you decide if a player should be allowed to sleep on the floor in this cell or not. + \item[Sleep Forbidden:] Can the player sleep on the floor? In most cities it is forbidden to sleep outside. Sleeping in the wild carries its + own risks of attack, though, and this entry lets you decide if a player should be allowed to sleep on the floor in this cell or not. - \item[Interior Water:] Should water be rendered in this interior cell? The game world consists of an endless ocean at height 0. Then the landscape is added. If part of the landscape goes below height 0, the player will see water. (See illustration.) + \item[Interior Water:] Should water be rendered in this interior cell? The game world consists of an endless ocean at height 0. Then the landscape + is added. If part of the landscape goes below height 0, the player will see water. (See illustration.) - Setting the cell's Interior Water to true tells the game that this cell is both an interior cell (inside a building, for example, rather than in the open air) but that there still needs to be water at height 0. This is useful for dungeons or mines that have water in them. + Setting the cell's Interior Water to true tells the game that this cell is both an interior cell (inside a building, for example, rather than + in the open air) but that there still needs to be water at height 0. This is useful for dungeons or mines that have water in them. - Setting the cell's Interior Water to false tells the game that the water at height 0 should not be used. Remember that cells that are in the outside world are exterior cells and should thus \textit{always} be set to false! + Setting the cell's Interior Water to false tells the game that the water at height 0 should not be used. Remember that cells that are in + the outside world are exterior cells and should thus \textit{always} be set to false! - \item[Interior Sky:] Should this interior cell have a sky? This is a rather unique case. The \textit{Tribunal} expansion took place in a city on the mainland. Normally this would require the city to be composed of exterior cells so it has a sky, weather and the like. But if the player is in an exterior cell and looks at his in-game map, he sees Vvardenfell with an overview of all exterior cells. The player would have to see the city's very own map, as if he was walking around in an interior cell. + \item[Interior Sky:] Should this interior cell have a sky? This is a rather unique case. The \TB expansion took place in a city on + the mainland. Normally this would require the city to be composed of exterior cells so it has a sky, weather and the like. But if the player is + in an exterior cell and looks at his in-game map, he sees Vvardenfell with an overview of all exterior cells. The player would have to see + the city's very own map, as if he was walking around in an interior cell. - So the developers decided to create a workaround and take a bit of both: The whole city would technically work exactly like an interior cell, but it would need a sky as if it was an exterior cell. That's what this is. This is why the vast majority of the cells you will find in this screen will have this option set to false: It's only meant for these "fake exteriors". + So the developers decided to create a workaround and take a bit of both: The whole city would technically work exactly like an interior cell, + but it would need a sky as if it was an exterior cell. That's what this is. This is why the vast majority of the cells you will find in this screen + will have this option set to false: It's only meant for these "fake exteriors". - \item[Region:] To which Region does this cell belong? This has an impact on the way the game handles weather and encounters in this area. It is also possible for a cell not to belong to any region. + \item[Region:] To which Region does this cell belong? This has an impact on the way the game handles weather and encounters in this area. + It is also possible for a cell not to belong to any region. \end{description} \subsubsection{Referenceables} - -This is a library of all the items, triggers, containers, NPCs, etc. in the game. There are several kinds of Record Types. Depending on which type a record is, it will need specific information to function. For example, an NPC needs a value attached to its aggression level. A chest, of course, does not. All Record Types contain at least a model. How else would the player see them? Usually they also have a Name, which is what you see when you hover your reticle over the object. +This is a library of all the items, triggers, containers, NPCs, etc. in the game. There are several kinds of Record Types. Depending on which type +a record is, it will need specific information to function. For example, an NPC needs a value attached to its aggression level. A chest, of course, +does not. All Record Types contain at least a model. How else would the player see them? Usually they also have a Name, which is what you see +when you hover your reticle over the object. Let's go through all Record Types and discuss what you can tell OpenCS about them. diff --git a/manual/opencs/windows.tex b/manual/opencs/windows.tex index 3bdfa63626..de377a119f 100644 --- a/manual/opencs/windows.tex +++ b/manual/opencs/windows.tex @@ -1,28 +1,53 @@ \section{Windows} \subsection{Introduction} -This section describes the multiple windows interface of the OpenCS editor. This design principle was chosen in order to extend the flexibility of the editor, especially on the multiple screens setups and on environments providing advanced windows management features, like; for instance; multiple desktops found commonly on many open source desktop environments. However, it is enough to have a single large screen to see the advantages of this concept.\\ -OpenCS windows interface is easy to describe and understand. In fact We decided to minimize use of many windows concepts applied commonly in various applications. For instance dialog windows are really hard to find in the OpenCS. You are free to try, though.\\ -Because of this, and the fact that we expect that user is familiar with other applications using windows this section is mostly focused on practical ways of organizing work with the OpenCS. +This section describes the multiple windows interface of the OpenCS editor. This design principle was chosen in order +to extend the flexibility of the editor, especially on the multiple screens setups and on environments providing advanced +windows management features, like; for instance: multiple desktops found commonly on many open source desktop environments. +However, it is enough to have a single large screen to see the advantages of this concept. + +OpenCS windows interface is easy to describe and understand. In fact we decided to minimize use of many windows concepts +applied commonly in various applications. For instance dialog windows are really hard to find in the OpenCS. You are free to try, +though. + +Because of this, and the fact that we expect that user is familiar with other applications using windows this section is mostly +focused on practical ways of organizing work with the OpenCS. \subsection{Basics} -After starting Open{CS} and choosing content files to use a editor window should show up. It probably does not look surprising: there is a menubar at the top, and there is a large empty area. That's it: a brand new Open{CS} window contains only menubar and statusbar. In order to make it a little bit more useful you probably want to enable some panels\footnote{Also known as widgets.}. You are free to do so, just try to explore the menubar. \\ -You probably founded out the way to enable and disable some interesting tables, but those will be described later. For now, let's just focus on the windows itself. +After starting Open{CS} and choosing content files to use a editor window should show up. It probably does not look surprising: +there is a menubar at the top, and there is a large empty area. That is it: a brand new Open{CS} window contains only menubar +and statusbar. In order to make it a little bit more useful you probably want to enable some panels\footnote{Also known as widgets.}. +You are free to do so, just try to explore the menubar. + +You probably founded out the way to enable and disable some interesting tables, but those will be described later. For now, let's +just focus on the windows itself. \paragraph{Creating new windows} -is easy! Just visit view menu, and use the ``New View'' item. Suddenly, out of the blue a new window will show up. As you would expect, it is also blank, and you are free to add any of the Open{CS} panels. +is easy! Just visit view menu, and use the ``New View'' item. Suddenly, out of the blue a new window will show up. As you would expect, +it is also blank, and you are free to add any of the Open{CS} panels. \paragraph{Closing opened window} -is also easy! Simply close that window decoration button. We suspect that you knew that already, but better to be sure. Closing last Open{CS} window will also terminate application session. +is also easy! Simply close that window decoration button. We suspect that you knew that already, but better to be sure. +Closing last Open{CS} window will also terminate application session. \paragraph{Multi-everything} -is the main foundation of Open{CS} interface. You are free to create as many windows as you want to, free to populate it with any panels you may want to, and move everything as you wish to -- even if it makes no sense at all. If you just got crazy idea and you are wonder if you are able to have one hundred Open{CS} windows showing panels of the same type, well most likely you are able to do so.\\ +is the main foundation of Open{CS} interface. You are free to create as many windows as you want to, free to populate it with +any panels you may want to, and move everything as you wish to -- even if it makes no sense at all. If you just got crazy idea and +you are wonder if you are able to have one hundred Open{CS} windows showing panels of the same type, well most likely you are +able to do so. -The principle behind this design decision is easy to see for Bethesda made editor, but maybe not so clear for users who are just about to begin their wonderful journey of modding.\\ +The principle behind this design decision is easy to see for \BS made editor, but maybe not so clear for users who are +just about to begin their wonderful journey of modding. \subsection{Advanced} -So why? Why this is created in such manner. The answer is frankly simple: because it is effective. When creating a mod, you often have to work only with just one table. For instance you are just balancing weapons damage and other statistics. It makes sense to have all the space for just that one table. More often, you are required to work with two and switch them from time to time. All major graphical environments commonly present in operating systems comes with switcher feature, that is a key shortcut to change active window. It is very effective and fast when you have only two windows, each holding only one table. Sometimes you have to work with two at the time, and with one from time to time. Here, you can have one window holding two tables, and second holding just one.\\ +So why? Why this is created in such manner. The answer is frankly simple: because it is effective. When creating a mod, you often +have to work only with just one table. For instance you are just balancing weapons damage and other statistics. It makes sense +to have all the space for just that one table. More often, you are required to work with two and switch them from time to time. +All major graphical environments commonly present in operating systems comes with switcher feature, that is a key shortcut to change +active window. It is very effective and fast when you have only two windows, each holding only one table. Sometimes you have to work +with two at the time, and with one from time to time. Here, you can have one window holding two tables, and second holding just one. -Open{CS} is designed to simply make sense and do not slowdown users. It is as simple as possible (but not simpler), and uses one flexible approach in all cases.\\ +Open{CS} is designed to simply make sense and do not slowdown users. It is as simple as possible (but not simpler), and uses one +flexible approach in all cases. There is no point in digging deeper in the windows of Open{CS}. Let's explore panels, starting with tables. From 2e5e9e0c44c6ca16470396e6c3d0ee50b6a34e35 Mon Sep 17 00:00:00 2001 From: Lukasz Gromanowski Date: Sun, 1 Dec 2013 01:15:46 +0100 Subject: [PATCH 383/434] Small official/unofficial style cleanup - missed that one. Signed-off-by: Lukasz Gromanowski --- manual/opencs/filters.tex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manual/opencs/filters.tex b/manual/opencs/filters.tex index e3781619c9..4eb0985a59 100644 --- a/manual/opencs/filters.tex +++ b/manual/opencs/filters.tex @@ -25,7 +25,7 @@ Don't be afraid though, filters are fairly intuitive and easy to use. \end{description} \subsubsection{Basics} -In fact you don't need to learn everything about filters in order to use them. In fact all you need to know to achieve decent productivity +In fact you do not need to learn everything about filters in order to use them. In fact all you need to know to achieve decent productivity with OpenCS is inside basics section. \subsubsection{Interface} From 3b64de7441a65be7b02e4954fc8a09f1a108512b Mon Sep 17 00:00:00 2001 From: Lukasz Gromanowski Date: Sun, 1 Dec 2013 03:18:08 +0100 Subject: [PATCH 384/434] Another part of reformatting and clean up. Signed-off-by: Lukasz Gromanowski --- manual/opencs/files_and_directories.tex | 42 +++++----- manual/opencs/filters.tex | 106 ++++++++++++------------ manual/opencs/main.tex | 14 ++-- manual/opencs/tables.tex | 32 +++---- manual/opencs/windows.tex | 24 +++--- 5 files changed, 111 insertions(+), 107 deletions(-) diff --git a/manual/opencs/files_and_directories.tex b/manual/opencs/files_and_directories.tex index ce686d56cb..b3283dac48 100644 --- a/manual/opencs/files_and_directories.tex +++ b/manual/opencs/files_and_directories.tex @@ -1,53 +1,53 @@ \section{Files and Directories} \subsection{Introduction} This section of the manual covers usage of files and directories by the OpenCS. Files and directories are file system concepts, -and you are probably already familiar with it. We won't try to explain this concepts, we will just focus on Open{CS}. +and you are probably already familiar with it. We won't try to explain this concepts, we will just focus on \OCS. \subsection{Used terms} %TODO \subsection{Basics} \paragraph{Directories} -Open{MW} and Open{CS} uses multiple directories on file systems. First of, there is a \textbf{user directory} that holds configuration +OpenMW and \OCS{} uses multiple directories on file systems. First of, there is a \textbf{user directory} that holds configuration files and few different folders. The location of the user directory is hard coded for each supported operating system. %TODO list paths. -In addition to this single hard coded directory, both Open{MW} and Open{CS} need a place to seek for actual data files of the game: +In addition to this single hard coded directory, both \OMW{} and \OCS{} need a~place to seek for actual data files of the game: textures, models, sounds and files that store records of objects in game; dialogues and so one -- so called content files. We support -multiple such paths (We call it \textbf{data paths}) as specified in the configuration. Usually one data path points to the directory -where original Morrowind\texttrademark is either installed or unpacked. You are free to specify as many data paths as you would like, +multiple such paths (we call it \textbf{data paths}) as specified in the configuration. Usually one data path points to the directory +where original \MW{} is either installed or unpacked. You are free to specify as many data paths as you would like, however, there is one special data path that, as described later, is used to store newly created content files. \paragraph{Content files} -\BS \MW engine is using two types of files: ESM (master) and ESP (plugin). The distinction between those +\BS{} \MW{} engine is using two types of files: ESM (master) and ESP (plugin). The distinction between those is not clear, and often confusing. You would expect the ESM (master) file is used to specify one master, that is modified by the ESPs plugins, and indeed: this is the basic idea. However, original expansions also were made as ESM files, even though they essentially could be described as a really large plugins, and therefore rather use ESP files. There were technical reasons behind this decision -- somewhat valid -in the case of original engine, but clearly it's better to create a system that can be used is more sensible way. Open{MW} achieves +in the case of original engine, but clearly it's better to create a system that can be used is more sensible way. \OMW{} achieves his with our own content file types. We support both ESM and ESP files, but in order to make use of new features of OpenMW one should consider using new file types designed with our engine in mind: game files and addon files together called ``content files``. -\subparagraph{Open{MW} content files} +\subparagraph{OpenMW content files} Game and Addon files are concept somewhat similar to the old ESM/ESP, only in the way it should be from the very beginning. Nothing easier -to describe. If you want to make new game using Open{MW} as engine (so called ``total conversion'') you should create a game file. +to describe. If you want to make new game using \OMW{} as engine (so called ``total conversion'') you should create a game file. If you want to create a addon for existing game file -- simply create addon file. Nothing else matters: The only distinction you should consider is if your project is about changing other game, or creating a new one. Simple as that. Other simple thing about content files are extensions. We are using .omwaddon for addon files and .omwgame for game files. %TODO describe what content files contains. and what not. -\subparagraph{\MW content files} -Using our content files is recommended solution for projects that are intended to used with Open{MW} engine. However some players -wish to use original Morrowind engine, even with it large flaws and lacking features\footnote{If this is actually wrong, We are very +\subparagraph{\MW{} content files} +Using our content files is recommended solution for projects that are intended to used with \OMW{} engine. However some players +wish to use original Morrowind engine, even with it large flaws and lacking features\footnote{If this is actually wrong, we are very successful project. Yay!}. Also, since 2002 thousands of ESP/ESM files were created, some with really outstanding content. -Because of this Open{CS} simply has no other choice but support ESP/ESM files. However, if you decided to choose ESP/ESM file instead +Because of this \OCS{} simply has no other choice but support ESP/ESM files. However, if you decided to choose ESP/ESM file instead using our own content file types you are most likely aim at the original engine compatibility. This subject is covered in the very last section of this manual. %not finished TODO add the said section. Most likely when more features are present. The actual creation of new files is described in the next chapter. Here we are gonna focus only on details that you need to know -in order to create your first Open{CS} file while full understanding your needs. For now let's jut remember that content files +in order to create your first \OCS{} file while full understanding your needs. For now let's jut remember that content files are created inside the user directory, in the the \textbf{data} subfolder (that is the one special data directory mentioned earlier). \subparagraph{Dependencies} @@ -65,7 +65,7 @@ for addon files -- it can not depend on addon files. %\subparagraph{Loading order} %TODO \paragraph{Project files} -Project files act as containers for data not used by the Open{MW} game engine itself, but still useful for OpenCS. The shining example +Project files act as containers for data not used by the \OMW{} game engine itself, but still useful for OpenCS. The shining example of this data category are without doubt record filters (described in the later section of the manual you are reading currently). As a mod author you probably do not need and/or want to distribute project files at all, they are meant to be used only by you. @@ -76,12 +76,12 @@ with appended extensions. For instance swords.omwaddon file is associated with s %TODO where are they stored. Project files are stored inside the user directory, in the \textbf{projects} subfolder. This is the path location for both freshly -created project files, and a place where Open{CS} looks for already existing files. +created project files, and a place where \OCS{} looks for already existing files. \paragraph{Resources files} %textures, sounds, whatever Unless we are talking about the fully text based game, like Zork or Rogue, you are expecting that a video game is using some media files: -models with textures, pictures acting as icons, sounds and everything else. Since content files, no matter if it is ESP, ESM or new Open{MW} +models with textures, pictures acting as icons, sounds and everything else. Since content files, no matter if it is ESP, ESM or new \OMW{} file type do not contain any of those, it is clear that they have to be deliver with a different file. It is also clear that this, let's call it ``resources file``, have to be supported by the engine. Without code handling those files, it is nothing more than a mathematical abstraction -- something, that lacks meaning for human beings\footnote{Unless we call programmers a human beings.}. @@ -89,7 +89,7 @@ Therefore this section must cover ways to add resources files to your content fi to do just that. Later, you will learn how to make use of those files in your content. \subparagraph{Audio} -Open{MW} is using {FF}mpeg for audio playback, and so we support every audio type that is supported by this library. This makes a huge list. +OpenMW is using {FFmpeg} for audio playback, and so we support every audio type that is supported by this library. This makes a huge list. Below is only small portion of supported file types. \begin{description} @@ -102,16 +102,16 @@ As in the case of audio files, we are using {FFmepg} to decode video files. The only the most significant. \begin{description} - \item bik videos used by original Morrowind game. + \item bik videos used by original \MW{} game. \item mp4 multimedia container which use more advanced codecs (MPEG-4 Parts 2,3,10) with a better audio and video compression rate, but also requiring more {CPU} intensive decoding -- this makes it probably less suited for video games. \item webm is a new, shiny and open source video format with excellent compression. It needs quite a lot of processing power to be decoded, - but since game logic is not running during cut scenes we can recommended it for use with Open{MW}. + but since game logic is not running during cut scenes we can recommended it for use with \OMW. \item ogv alternative, open source container using theora codec for video and vorbis for audio. \end{description} \subparagraph{Textures and images} -Original \MW game uses DDS and TGA files for all kind of two dimensional images and textures alike. In addition, engine supported BMP +Original \MW{} game uses DDS and TGA files for all kind of two dimensional images and textures alike. In addition, engine supported BMP files for some reason (BMP is a terrible format for a video game). We also support extended set of image files -- including JPEG and PNG. JPEG and PNG files can be useful in some cases, for instance JPEG file is a valid option for skybox texture and PNG can useful for masks. However please, keep in mind that JPEG can grow into large sizes quickly and are not the best option with DirectX rendering backend. diff --git a/manual/opencs/filters.tex b/manual/opencs/filters.tex index 4eb0985a59..802aa3ec5f 100644 --- a/manual/opencs/filters.tex +++ b/manual/opencs/filters.tex @@ -1,16 +1,16 @@ -\section{Record filters} +d\section{Record filters} \subsection{Introduction} -Filters are the key element of OpenCS use cases by allowing rapid and easy access to the searched records presented in all tables. +Filters are the key element of \OCS{} use cases by allowing rapid and easy access to the searched records presented in all tables. Therefore: in order to use this application fully effective you should make sure that all concepts and instructions written in the this section of the manual are perfectly clear to you. -Don't be afraid though, filters are fairly intuitive and easy to use. +Do not be afraid though, filters are fairly intuitive and easy to use. \subsubsection{Used Terms} \begin{description} \item[Filter] is generally speaking a tool able to ``Filter'' (that is: select some elements, while discarding others) according - to the some criteria. In case of OpenCS: records are being filtered according to the criteria of user choice. Criteria are written + to the some criteria. In case of \OCS: records are being filtered according to the criteria of user choice. Criteria are written down in language with simple syntax. \item[Criteria] describes condition under with any any record is being select by the filter. \item[Syntax] as you may noticed computers (in general) are rather strict, and expect only strictly formulated orders -- that is: @@ -18,20 +18,20 @@ Don't be afraid though, filters are fairly intuitive and easy to use. \item[Expression] is way we are actually performing filtering. Filter can be treated as ``functions'': accepts arguments, and evaluates either to the true or false for every column record at the time. \item[N-ary] is any expression that expects one or more expressions as arguments. It is useful for grouping two (or more) other expressions - together in order to create filter that will check for criteria placed in two (again: or more) columns (logical ``or'', ``and''). - \item[unary] is any expression that expects one other expression. The example is ``not'' expression. In fact ``not'' is the only useful - unary expression in OpenCS record filters. + together in order to create filter that will check for criteria placed in two (again: or more) columns (logical \textit{or}, \textit{and}). + \item[unary] is any expression that expects one other expression. The example is \textit{not} expression. In fact \textit{not} is the only useful + unary expression in \OCS{} record filters. \item[nullary] is expression that does not accepts other expressions. It accepts arguments specified later. \end{description} \subsubsection{Basics} In fact you do not need to learn everything about filters in order to use them. In fact all you need to know to achieve decent productivity -with OpenCS is inside basics section. +with \OCS{} is inside basics section. \subsubsection{Interface} -Above each table there is a field that is used to enter filter: either predefined by the OpenMW developers or made by you, the user. +Above each table there is a field that is used to enter filter: either predefined by the \OMW{} developers or made by you, the user. You probably noticed it before. However there is also completely new element, although using familiar table layout. Go to the application -menu view, and click filters. You should see set of default filters, made by the OpenMW team in the table with the following columns: filter, +menu view, and click filters. You should see set of default filters, made by the \OMW{} team in the table with the following columns: filter, description and modified. \begin{description} @@ -44,20 +44,21 @@ description and modified. So let's learn how to actually use those to speed up your work. \subsubsection{Using predefined filters} Using those filters is quite easy and involves typing inside the filter field above the table. For instance, try to open referencables -table and type in the filters field the following: ``project::weapons''. As soon as you complete the text, table will magicly alter -and will show only the weapons. As you could noticed project::weapons is nothing else than a ID of one of the predefined filters. That is it: +table and type in the filters field the following: \mono{project::weapons}. As soon as you complete the text, table will magicly alter +and will show only the weapons. As you could noticed \mono{project::weapons} is nothing else than a~ID of one of the predefined filters. That is it: in order to use the filter inside the table you simply type it is name inside the filter field. To make life easier filter IDs follow simple convention. \begin{itemize} - \item Filter ID filtering a specific record type contains usually the name of a specific group. For instance project::weapons filter + \item Filter ID filtering a specific record type contains usually the name of a specific group. For instance \mono{project::weapons} filter contains the word weapons (did you noticed?). Plural form is always used. \item When filtering specific subgroup the ID starts just like in the case of general filter. For instance project::weaponssilver will - filter only silver weapons (new mechanic introduced by the Bloodmoon, silver weapons deal double damage against werewolfs) and - project::weaponsmagical will filter only magical weapons (able to hurt ghosts and other supernatural creatures). - \item There are few exceptions from the above rule. For instance there is a project::added, project::removed, project::modyfied, project::base. - You would probably except something more like ``project::statusadded'' but in this case typing this few extra characters would only + filter only silver weapons (new mechanic introduced by the \BM{}, silver weapons deal double damage against werewolfs) and + \mono{project::weaponsmagical} will filter only magical weapons (able to hurt ghosts and other supernatural creatures). + \item There are few exceptions from the above rule. For instance there is a \mono{project::added}, \mono{project::removed}, + \mono{project::modyfied}, \mono{project::base}. + You would probably except something more like \mono{project::statusadded} but in this case typing this few extra characters would only help to break your keyboard faster. \end{itemize} @@ -71,13 +72,13 @@ Finally, you will have to write this with correct syntax. As a result table will Advance subsection covers everything that you need to know in order to create any filter you may want to %TODO the filter part is actually wrong \subsubsection{Namespaces} -Did you noticed that every default filter has ``project::`` prefix? It is a ``namespace``, a term borrowed from the \CPP{} language. -In case of OpenCS namespace always means scope of the said object\footnote{You are not supposed to understand this at the moment.}. +Did you noticed that every default filter has \mono{project::} prefix? It is a \textit{namespace}, a~term borrowed from the \CPP{} language. +In case of \OCS{} namespace always means scope of the said object\footnote{You are not supposed to understand this at the moment.}. But what does it mean in case of filters? Well, short explanation is actually simple. \begin{description} - \item[project::] namespace indicates that filter is used with the project, in multiple sessions. You can restart Open{CS} and filter + \item[project::] namespace indicates that filter is used with the project, in multiple sessions. You can restart \OCS{} and filter is still there. - \item[session::] namespace indicates that filter is not stored trough multiple sessions and once you will quit Open{CS} (close session) + \item[session::] namespace indicates that filter is not stored trough multiple sessions and once you will quit \OCS{} (close session) the filter will be gone. Forever! Until then it can be found inside the filters table. \end{description} In addition to this two scopes, there is a third one; called one-shot. One-shot filters are not stored (even during single session) @@ -89,8 +90,8 @@ Still, you may wonder how you are supposed to write expressions, what expression with nullary expressions that will allow you to create a basic filter. \subsubsection{Nullary expressions} -All nullary expressions are used in similar manner. First off: you have to write it is name (for instance: ``string'') and secondly: -condition that will be checked inside brackets (for instance string(something, something)). If conditions of your expression will be meet +All nullary expressions are used in similar manner. First off: you have to write it is name (for instance: \mono{string}) and secondly: +condition that will be checked inside brackets (for instance \mono{string(something, something)}). If conditions of your expression will be meet by a record (technical speaking: expression will evaluate to true) the record will show up in the table. It is clear that you need to know what are you checking, that is: what column of the table contains information that you are interested @@ -99,27 +100,27 @@ you want to see, while the second one sets desired value inside of the cell. To \paragraph{String -- string(``column'', ``value'')} String in programmers language is often\footnote{Often, not always. There are different programming languages using slightly different terms.} -just a word for anything composed of characters. In case of OpenCS this is in fact true for every value inside the column that is not composed -of the pure numbers. Even columns containing only ``true`` and ``false`` values can be targeted by the string expression. -\footnote{There is no Boolean (''true'' or ``false'') value in the OpenCS. You should use string for those.} String evaluates to true, +just a word for anything composed of characters. In case of \OCS{} this is in fact true for every value inside the column that is not composed +of the pure numbers. Even columns containing only ``true`` and ``false`` values can be targeted by the string expression\footnote{There is no +Boolean (''true'' or ``false'') value in the \OCS. You should use string for those.}. String evaluates to true, when record contains in the specified column exactly the same value as specified. Since majority of the columns contain string values, string is among the most often used expressions. Examples: \begin{itemize} - \item string(``Record Type'', ``Weapon'') -- will evaluate to true for all records containing ``Weapon'' in the ``Record Type'' column cell. + \item \mono{string(``Record Type'', ``Weapon'')} -- will evaluate to true for all records containing \mono{Weapon} in the \mono{Record Type} column cell. This group contains every weapon (including arrows and bolts) found in the game. - \item string(``Portable'', ``true'') -- will evaluate to true for all records containing word true inside ``Portable'' column cell. + \item \mono{string(``Portable'', ``true'')} -- will evaluate to true for all records containing word true inside \mono{Portable} column cell. This group contains every portable light sources (lanterns, torches etc.). \end{itemize} This is probably enough to create around 90 string filters you will eventually need. However, this expression is even more powerful -- it accepts regular expressions (also called regexps). Regular expressions is a way to create string criteria that will be matched by one than just one specific value in the column. For instance, you can display both left and right gauntlets with the following expression: -``string("armor type", ".* gauntlet"))`` because ''.*'' in regexps means just: ``anything''. This filter says: please, show me ``any'' gauntlet. +\mono{string("armor type", ".* gauntlet"))} because \mono{.*} in regexps means just: ``anything''. This filter says: please, show me ``any'' gauntlet. There are left and right gauntlets in the morrowind so this will evaluate to true for both. Simple, isn't it? -Creating regexps can be a difficult and annoying -- especially when you need complex criteria. On the other hand, We are under impression +Creating regexps can be a difficult and annoying -- especially when you need complex criteria. On the other hand, we are under impression that in reality complex expressions are needed only in sporadic cases. In fact, the truth is: that most of the time only already mentioned -``.*'' is needed and therefore the following description of regexps can be skipped by vast majority of readers. +\mono{.*} is needed and therefore the following description of regexps can be skipped by vast majority of readers. Before working with Regular Expressions, you should understand what actually are regular expressions. Essentially, the idea is simple: when you are writing any word, you are using strictly defined letters -- that is: letters create a word. What you want to do with regular @@ -127,21 +128,22 @@ expression is to use set of rules that will match to many words. It is not that you will clearly need way to determinate what letters you want to match (word is composed by letters). Before introducing other ways to choose between characters, I want explain anchors. Anchors allows you to decide where to ``look'' in the string. -You surely should know about ``\^'' anchor and ``\textdollar''. Putting ``\^`` will tell to Open{CS} to look on the beginning of string, -while ''\textdollar`` is used to mark the end of it. For instance, pattern ''\^Pink.* elephant.\textdollar`` Will match any sentence beginning -with the word ''Pink`` and ending with '' elephant.``. Pink fat elephant. Pink cute elephant. It does not matter what is in between, -because ''.*`` is used.\\ +You surely should know about \mono{\textasciicircum} anchor and \mono{\textdollar}. Putting \mono{\textasciicircum} will tell to \OCS{} +to look on the beginning of string, while \mono{\textdollar} is used to mark the end of it. For instance, pattern +\mono{\textasciicircum{}Pink.* elephant.\textdollar} will match any sentence beginning with the word \mono{Pink} and ending with +\mono{ elephant.}. Pink fat elephant. Pink cute elephant. It does not matter what is in between, because \mono{.*} is used. -You have already seen the power of the simple ``.*''. But what if you want to chose between only two (or more) letters? Well, this is when -``[|]'' comes in handy. If you write something like: ``\^[a|k].*'' you are simply telling Open{CS} to filter anything that starts with either -``a'' or ``k''. Using ``\^[a|k|l].*'' will work in the same manner, but it will also cover strings starting with ``l''. +You have already seen the power of the simple \mono{.*}. But what if you want to chose between only two (or more) letters? Well, this is when +\mono{[|]} comes in handy. If you write something like: \mono{\textasciicircum[a|k].*} you are simply telling \OCS{} to filter anything that +starts with either \mono{a} or \mono{k}. Using \mono{\textasciicircum[a|k|l].*} will work in the same manner, but it will also cover +strings starting with \mono{l}. -What if you want to match more than just one latter? Just use ``(|)``. it is pretty similar to the above one letter as you see, but it is -used to fit more than just one character. For instance: ''\^(Pink|Green).* (elephant|crocodile).\textdollar`` will be true for all sentences -starting with ''Pink`` or ''Green`` and ending with either ''elephant.`` or ''crocodile.``. +What if you want to match more than just one latter? Just use \mono{(|)}. it is pretty similar to the above one letter as you see, but it is +used to fit more than just one character. For instance: \mono{\textasciicircum(Pink|Green).* (elephant|crocodile).\textdollar} will be +true for all sentences starting with \mono{Pink} or \mono{Green} and ending with either \mono{elephant.} or \mono{crocodile.}. Regular expressions are not the main topic of this manual. If you wish to learn more on this subject please, read the documentation on -Qt regular expressions syntax, or TRE regexp syntax (it is almost like in Qt). Above is just enough to use Open{CS} effectively to be sure. +Qt regular expressions syntax, or TRE regexp syntax (it is almost like in Qt). Above is just enough to use \OCS{} effectively to be sure. \paragraph{Value -- value(``value'', (``open'', ``close''))} While string expression covers vast group of columns containing string values, there are in fact columns with just numerical values like @@ -149,10 +151,10 @@ While string expression covers vast group of columns containing string values, t inside brackets. Clearly, conditions should hold column to test in. However in this case wanted value is specified as a range. As you would imagine the range can be specified as including a border value, or excluding. We are using two types of brackets for this: \begin{itemize} - \item To include value use [] brackets. For value equal 5, expression value(something, [5, 10]) will evaluate to true. - \item To exclude value use () brackets. For value equal 5, expression value(something, (5, 10)) will evaluate to false. - \item Mixing brackets is completely legal. For value equal 10, expression value(something, [5, 10) will evaluate to true. The same expression - will evaluate to false for value equal 10. + \item To include value use [] brackets. For value equal 5, expression \mono{value(something, [5, 10])} will evaluate to true. + \item To exclude value use () brackets. For value equal 5, expression \mono{value(something, (5, 10))} will evaluate to false. + \item Mixing brackets is completely legal. For value equal 10, expression \mono{value(something, [5, 10)} will evaluate to true. + The same expression will evaluate to false for value equal 10. \end{itemize} \paragraph{''true`` and ''false``} @@ -161,7 +163,7 @@ and false (in case of \textit{false}) no matter what. The main usage of this ex disable some part of the filter that makes heavy use of the logical expressions. \subsubsection{Logical expressions} -This subsection takes care of two remaining groups of expressions: binary and unary. The only unary expression present in the OpenCS is logical +This subsection takes care of two remaining groups of expressions: binary and unary. The only unary expression present in the \OCS{} is logical \textit{not}, while the remaining binary expressions are: \textit{or}, \textit{and}. This clearly makes them (from the user point of view) belonging to the same group of logical expressions. @@ -170,8 +172,8 @@ Sometimes you may be in need of reversing the output of the expression. This is expression will revert it: if expression was returning true, it will return false; if it was returning false, it will return true. Brackets are not needed: \textit{not} will revert only the first expression following it. -To show this on know example, let's consider the ''string("armor type", ".* gauntlet"))`` filter. As we mentioned earlier this will return true -for every gauntlet found in game. In order to show everything, but gauntlets we simply do ''not string("armor type", ".* gauntlet"))``. +To show this on know example, let's consider the \mono{string("armor type", ".* gauntlet"))} filter. As we mentioned earlier this will return true +for every gauntlet found in game. In order to show everything, but gauntlets we simply do \mono{not string("armor type", ".* gauntlet"))}. This is probably not the most useful filter on earth, but this is not a surprise: real value of \textit{not} expression shines when combined with \textit{or}, \textit{and} filters. @@ -179,13 +181,13 @@ This is probably not the most useful filter on earth, but this is not a surprise \textit{Or} is a expression that will return true if one of the arguments evaluates to true. You can use two or more arguments, separated by the comma. \textit{Or} expression is useful when showing two different group of records is needed. For instance the standard actor filter is using the following -''or(string(``record type'', npc), string(``record type'', creature))`` and will show both npcs and creatures. +\mono{or(string(``record type'', npc), string(``record type'', creature))} and will show both npcs and creatures. \paragraph{and -- and(expression1(), expression2())} \textit{And} is a expression that will return true if all arguments evaluates to true. As in the case of ''or`` you can use two or more arguments, separated by a comma. As we mentioned earlier in the \textit{not} filter, combining \textit{not} with \textit{and} can be very useful. For instance to show all armor types, -excluding gauntlets you can write the following: ''and (not string("armor type", ".* gauntlet"), string(''Record Type``, ''Armor``))''. +excluding gauntlets you can write the following: \mono{and (not string("armor type", ".* gauntlet"), string(''Record Type``, ''Armor``))}. \subsubsection{Creating and saving filter} In order to create and save new filter, you should go to the filters table, right click and select option ``add record'' from the context menu. @@ -196,7 +198,7 @@ and write it down there. Done! You are free to use your filter. \subsubsection{Replacing the default filters set} -{OpenCS} allows you to substitute default filters set provided by us, with your own filters. In order to do so you should create a new project, +OpenCS allows you to substitute default filters set provided by us, with your own filters. In order to do so you should create a new project, add desired filters, remove undesired and save. Rename the file to the ``defaultfilters'' (do not forget to remove .omwaddon.project extension) and place it inside your configuration directory. diff --git a/manual/opencs/main.tex b/manual/opencs/main.tex index 3a395e277d..1d8aebe408 100644 --- a/manual/opencs/main.tex +++ b/manual/opencs/main.tex @@ -3,7 +3,7 @@ \usepackage{babel} \usepackage{txfonts} % Public Times New Roman text & math font \usepackage[pdftex]{graphicx} -\usepackage[colorlinks,pdftex]{hyperref} +\usepackage[final,colorlinks,pdftex,pdfpagelabels=true]{hyperref} \author{OpenMW Team} \def\pdfBorderAttrs{/Border [0 0 0] } % No border arround Links @@ -13,11 +13,13 @@ pdfauthor={Copyright \textcopyright{} OpenMW Team }, pdftitle={OpenCS user manual} } - -\def\MW{\textit{Morrowind\texttrademark{}\ }} -\def\TB{\textit{Tribunal\ }} -\def\BM{\textit{Bloodmon\ }} -\def\BS{Bethesda Softworks\ } +\def\mono{\texttt} +\def\MW{\textit{Morrowind\texttrademark{}}} +\def\TB{\textit{Tribunal}} +\def\BM{\textit{Bloodmon}} +\def\BS{Bethesda Softworks} +\def\OMW{\hbox{OpenMW}} +\def\OCS{\hbox{OpenCS}} \begin{document} diff --git a/manual/opencs/tables.tex b/manual/opencs/tables.tex index cbb59b2dd7..e7cc06735a 100644 --- a/manual/opencs/tables.tex +++ b/manual/opencs/tables.tex @@ -1,8 +1,8 @@ \section{Tables} \subsection{Introduction} -If you have launched OpenCS already and played around with it for a bit, you have probably gotten the impression that it contains lots of tables. -You'd be spot on: OpenCS is built around using tables. This does not mean it works just like Microsoft Excel or Libre Office Calc, though. +If you have launched \OCS{} already and played around with it for a bit, you have probably gotten the impression that it contains lots of tables. +You'd be spot on: \OCS{} is built around using tables. This does not mean it works just like Microsoft Excel or Libre Office Calc, though. Due to the vast amounts of information involved with \MW, tables just made the most sense. You have to be able to spot information quickly and be able to change them on the fly. Let's browse through the various screens and see what all these tables show. @@ -12,7 +12,7 @@ Let's browse through the various screens and see what all these tables show. \subsubsection{Glossary} \begin{description} - \item[Record:] An entry in OpenCS representing an item, location, sound, NPC or anything else. + \item[Record:] An entry in \OCS{} representing an item, location, sound, NPC or anything else. \item[Reference, Referenceable:] When an item is placed in the world, it does not create a new record each time. For example, the game world might contain a lot of exquisite belts on different NPCs and in many crates, but they all refer to one specific record: the Exquisite Belt record. @@ -22,17 +22,17 @@ Let's browse through the various screens and see what all these tables show. \end{description} \subsubsection{Recurring Terms} -Some columns are recurring throughout OpenCS. They show up in (nearly) every table in OpenCS. +Some columns are recurring throughout \OCS. They show up in (nearly) every table in \OCS. \begin{description} -\item[ID] Each item, location, sound, etc. gets the same unique identifier in both OpenCS and Morrowind. This is usually a very self-explanatory name. +\item[ID] Each item, location, sound, etc. gets the same unique identifier in both \OCS{} and \MW. This is usually a very self-explanatory name. For example, the ID for the (unique) black pants of Caius Cosades is ``Caius\_pants''. This allows you to manipulate the game in many ways. For example, you could add these pants to your inventory by simply opening the console and write: ``player->addItem Caius\_pants''. Either way, in both Morrowind -and OpenCS, the ID is the primary way to identify all these different parts of the game. %Wrong! Cells do not have ID, only name. +and \OCS, the ID is the primary way to identify all these different parts of the game. %Wrong! Cells do not have ID, only name. \item[Modified] This column shows what has happened (if something has happened) to this record. There are four possible states in which it can exist. \item[Base] means that this record is part of the base game and is in its original state. Usually, if you create a mod, the base game is Morrowind with optionally the Bloodmoon and Tribunal expansions. -\item[Added] means that this record was not in the base game and has been added by a modder. +\item[Added] means that this record was not in the base game and has been added by a~modder. \item[Modified] means that the record is part of the base game, but has been changed in some way. \item[Deleted] means that this record used to be part of the base game, but has been removed as an entry. This does not mean, however, that the occurrences in the game itself have been removed! For example, if you remove the CharGen\_Bed entry from morrowind.esm, it does not mean the bedroll in the basement @@ -49,7 +49,7 @@ This describes the general areas of Vvardenfell. Each of these areas has differe \begin{description} \item[Name:] This is how the game will show your location in-game. \item[Map Colour:] This is a six-digit hexidecimal representation of the colour used to identify the region on the map available in - World > Region Map. If you don't have an application with a colour picker, you can use your favourite search engine to find a colour picker online. + World > Region Map. If you do not have an application with a colour picker, you can use your favourite search engine to find a colour picker online. \item[Sleep Encounter:] These are the rules for what kind of enemies you might encounter when you sleep outside in the wild. \end{description} @@ -59,8 +59,8 @@ why would the computer need to keep track the exact locations of NPCs walking th be quite useless and bring your system to its knees! So the world has been divided up into squares we call "cells". Once your character enters a cell, the game will load everything that is going on in that cell so you can interact with it. -In the original \MW this could be seen when you were travelling and you would see a small loading bar at the bottom of the screen; -you had just entered a new cell and the game would have to load all the items and NPCs. The Cells screen in OpenCS provides you with a list of cells +In the original \MW{} this could be seen when you were travelling and you would see a small loading bar at the bottom of the screen; +you had just entered a new cell and the game would have to load all the items and NPCs. The Cells screen in \OCS{} provides you with a list of cells in the game, both the interior cells (houses, dungeons, mines, etc.) and the exterior cells (the outside world). \begin{description} @@ -76,14 +76,14 @@ in the game, both the interior cells (houses, dungeons, mines, etc.) and the ext Setting the cell's Interior Water to false tells the game that the water at height 0 should not be used. Remember that cells that are in the outside world are exterior cells and should thus \textit{always} be set to false! - \item[Interior Sky:] Should this interior cell have a sky? This is a rather unique case. The \TB expansion took place in a city on + \item[Interior Sky:] Should this interior cell have a sky? This is a rather unique case. The \TB{} expansion took place in a city on the mainland. Normally this would require the city to be composed of exterior cells so it has a sky, weather and the like. But if the player is in an exterior cell and looks at his in-game map, he sees Vvardenfell with an overview of all exterior cells. The player would have to see the city's very own map, as if he was walking around in an interior cell. So the developers decided to create a workaround and take a bit of both: The whole city would technically work exactly like an interior cell, - but it would need a sky as if it was an exterior cell. That's what this is. This is why the vast majority of the cells you will find in this screen - will have this option set to false: It's only meant for these "fake exteriors". + but it would need a sky as if it was an exterior cell. That is what this is. This is why the vast majority of the cells you will find in this screen + will have this option set to false: It is only meant for these "fake exteriors". \item[Region:] To which Region does this cell belong? This has an impact on the way the game handles weather and encounters in this area. It is also possible for a cell not to belong to any region. @@ -93,11 +93,11 @@ in the game, both the interior cells (houses, dungeons, mines, etc.) and the ext \subsubsection{Referenceables} This is a library of all the items, triggers, containers, NPCs, etc. in the game. There are several kinds of Record Types. Depending on which type a record is, it will need specific information to function. For example, an NPC needs a value attached to its aggression level. A chest, of course, -does not. All Record Types contain at least a model. How else would the player see them? Usually they also have a Name, which is what you see +does not. All Record Types contain at least a~model. How else would the player see them? Usually they also have a Name, which is what you see when you hover your reticle over the object. -Let's go through all Record Types and discuss what you can tell OpenCS about them. +Let's go through all Record Types and discuss what you can tell \OCS{} about them. \begin{description} - \item[Activator:] This is an item that, when activated, starts a script or even just shows a tooltip. + \item[Activator:] This is an item that, when activated, starts a script or even just shows a~tooltip. \end{description} \ No newline at end of file diff --git a/manual/opencs/windows.tex b/manual/opencs/windows.tex index de377a119f..98bf369e85 100644 --- a/manual/opencs/windows.tex +++ b/manual/opencs/windows.tex @@ -1,20 +1,20 @@ \section{Windows} \subsection{Introduction} -This section describes the multiple windows interface of the OpenCS editor. This design principle was chosen in order +This section describes the multiple windows interface of the \OCS{} editor. This design principle was chosen in order to extend the flexibility of the editor, especially on the multiple screens setups and on environments providing advanced windows management features, like; for instance: multiple desktops found commonly on many open source desktop environments. However, it is enough to have a single large screen to see the advantages of this concept. OpenCS windows interface is easy to describe and understand. In fact we decided to minimize use of many windows concepts -applied commonly in various applications. For instance dialog windows are really hard to find in the OpenCS. You are free to try, +applied commonly in various applications. For instance dialog windows are really hard to find in the \OCS. You are free to try, though. Because of this, and the fact that we expect that user is familiar with other applications using windows this section is mostly -focused on practical ways of organizing work with the OpenCS. +focused on practical ways of organizing work with the \OCS. \subsection{Basics} -After starting Open{CS} and choosing content files to use a editor window should show up. It probably does not look surprising: -there is a menubar at the top, and there is a large empty area. That is it: a brand new Open{CS} window contains only menubar +After starting \OCS{} and choosing content files to use a editor window should show up. It probably does not look surprising: +there is a menubar at the top, and there is a~large empty area. That is it: a brand new \OCS{} window contains only menubar and statusbar. In order to make it a little bit more useful you probably want to enable some panels\footnote{Also known as widgets.}. You are free to do so, just try to explore the menubar. @@ -23,19 +23,19 @@ just focus on the windows itself. \paragraph{Creating new windows} is easy! Just visit view menu, and use the ``New View'' item. Suddenly, out of the blue a new window will show up. As you would expect, -it is also blank, and you are free to add any of the Open{CS} panels. +it is also blank, and you are free to add any of the \OCS{} panels. \paragraph{Closing opened window} is also easy! Simply close that window decoration button. We suspect that you knew that already, but better to be sure. -Closing last Open{CS} window will also terminate application session. +Closing last \OCS{} window will also terminate application session. \paragraph{Multi-everything} -is the main foundation of Open{CS} interface. You are free to create as many windows as you want to, free to populate it with +is the main foundation of \OCS{} interface. You are free to create as many windows as you want to, free to populate it with any panels you may want to, and move everything as you wish to -- even if it makes no sense at all. If you just got crazy idea and -you are wonder if you are able to have one hundred Open{CS} windows showing panels of the same type, well most likely you are +you are wonder if you are able to have one hundred \OCS{} windows showing panels of the same type, well most likely you are able to do so. -The principle behind this design decision is easy to see for \BS made editor, but maybe not so clear for users who are +The principle behind this design decision is easy to see for \BS{} made editor, but maybe not so clear for users who are just about to begin their wonderful journey of modding. \subsection{Advanced} @@ -46,9 +46,9 @@ All major graphical environments commonly present in operating systems comes wit active window. It is very effective and fast when you have only two windows, each holding only one table. Sometimes you have to work with two at the time, and with one from time to time. Here, you can have one window holding two tables, and second holding just one. -Open{CS} is designed to simply make sense and do not slowdown users. It is as simple as possible (but not simpler), and uses one +OpenCS is designed to simply make sense and do not slowdown users. It is as simple as possible (but not simpler), and uses one flexible approach in all cases. -There is no point in digging deeper in the windows of Open{CS}. Let's explore panels, starting with tables. +There is no point in digging deeper in the windows of \OCS. Let's explore panels, starting with tables. %We should write some tips and tricks here. \ No newline at end of file From 586990848041801ce2962bdd5f6ceb740240ab92 Mon Sep 17 00:00:00 2001 From: Lukasz Gromanowski Date: Sun, 1 Dec 2013 12:03:04 +0100 Subject: [PATCH 385/434] Another small cleanup - missed that in previous commits. Signed-off-by: Lukasz Gromanowski --- manual/opencs/files_and_directories.tex | 2 +- manual/opencs/filters.tex | 4 ++-- manual/opencs/windows.tex | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/manual/opencs/files_and_directories.tex b/manual/opencs/files_and_directories.tex index b3283dac48..d13cfa7270 100644 --- a/manual/opencs/files_and_directories.tex +++ b/manual/opencs/files_and_directories.tex @@ -106,7 +106,7 @@ only the most significant. \item mp4 multimedia container which use more advanced codecs (MPEG-4 Parts 2,3,10) with a better audio and video compression rate, but also requiring more {CPU} intensive decoding -- this makes it probably less suited for video games. \item webm is a new, shiny and open source video format with excellent compression. It needs quite a lot of processing power to be decoded, - but since game logic is not running during cut scenes we can recommended it for use with \OMW. + but since game logic is not running during cut scenes we can recommend it for use with \OMW. \item ogv alternative, open source container using theora codec for video and vorbis for audio. \end{description} diff --git a/manual/opencs/filters.tex b/manual/opencs/filters.tex index 802aa3ec5f..a5f88aebba 100644 --- a/manual/opencs/filters.tex +++ b/manual/opencs/filters.tex @@ -1,4 +1,4 @@ -d\section{Record filters} +\section{Record filters} \subsection{Introduction} Filters are the key element of \OCS{} use cases by allowing rapid and easy access to the searched records presented in all tables. Therefore: in order to use this application fully effective you should make sure that all concepts and instructions written in @@ -53,7 +53,7 @@ To make life easier filter IDs follow simple convention. \begin{itemize} \item Filter ID filtering a specific record type contains usually the name of a specific group. For instance \mono{project::weapons} filter contains the word weapons (did you noticed?). Plural form is always used. - \item When filtering specific subgroup the ID starts just like in the case of general filter. For instance project::weaponssilver will + \item When filtering specific subgroup the ID starts just like in the case of general filter. For instance \mono{project::weaponssilver} will filter only silver weapons (new mechanic introduced by the \BM{}, silver weapons deal double damage against werewolfs) and \mono{project::weaponsmagical} will filter only magical weapons (able to hurt ghosts and other supernatural creatures). \item There are few exceptions from the above rule. For instance there is a \mono{project::added}, \mono{project::removed}, diff --git a/manual/opencs/windows.tex b/manual/opencs/windows.tex index 98bf369e85..97bb75e792 100644 --- a/manual/opencs/windows.tex +++ b/manual/opencs/windows.tex @@ -2,7 +2,7 @@ \subsection{Introduction} This section describes the multiple windows interface of the \OCS{} editor. This design principle was chosen in order to extend the flexibility of the editor, especially on the multiple screens setups and on environments providing advanced -windows management features, like; for instance: multiple desktops found commonly on many open source desktop environments. +windows management features, like for instance: multiple desktops found commonly on many open source desktop environments. However, it is enough to have a single large screen to see the advantages of this concept. OpenCS windows interface is easy to describe and understand. In fact we decided to minimize use of many windows concepts From 8b74e8cba7da123119217d88fd7d18da627de826 Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Sun, 1 Dec 2013 13:00:44 +0100 Subject: [PATCH 386/434] Commiting. Not sure what since i had short blackout :/ --- manual/opencs/creating_file.tex | 25 +++++++++++++++++++++++++ manual/opencs/files_and_directories.tex | 19 ++++++++++--------- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/manual/opencs/creating_file.tex b/manual/opencs/creating_file.tex index e69de29bb2..8cab726174 100644 --- a/manual/opencs/creating_file.tex +++ b/manual/opencs/creating_file.tex @@ -0,0 +1,25 @@ +\section{OpenCS starting dialog} +\subsection{Introduction} +The great day has come. Today, you shall open \OCS{} application. And when you do this, you shall see our starting dialog window that holds three buttons +that can bring both pain and happiness. So just do this, please. + +\subsection{Basics} +Back to the manual? Great! As you can see, the starting window holds just three buttons. Since you are already familiar with our files system, they come +to you with no surprise.\\ + +First, there is a \textbf{Create A New Game} button. Clearly, you should press it when you want to create a game file. Than, what \textbf{Create A New Addon} button do? +Yes! You are right! This button will create any addon content file (and new project file associated with it)! Wonderful! And what the last remaining button do? \textbf{Edit A Content File}? Well, it comes with no surprise that this should be used when you need to alter existing content file, either a game or addon.\\ + +\paragraph{Selecting Files} +As We wrote earlier, + +\subsection{Advanced} +If you are paying attention, you noticed any extra icon with wrench. This one will open small settings window. Options here are few, and easy to list. +\begin{description} + \item {Window Size} is needed to configure initial window size of the starting window. + \item {Display format} can be used to configure behavior of the \OCS{} itself, however only in the limited degree. You can decide if you want \OCS{} to display icon, text or both in some columns of tables. +\end{description} + +%TODO configuration + +And that would be it. There is no point spending more time here. We should go forward now. \ No newline at end of file diff --git a/manual/opencs/files_and_directories.tex b/manual/opencs/files_and_directories.tex index b3283dac48..bb399ce991 100644 --- a/manual/opencs/files_and_directories.tex +++ b/manual/opencs/files_and_directories.tex @@ -24,7 +24,7 @@ is not clear, and often confusing. You would expect the ESM (master) file is use and indeed: this is the basic idea. However, original expansions also were made as ESM files, even though they essentially could be described as a really large plugins, and therefore rather use ESP files. There were technical reasons behind this decision -- somewhat valid in the case of original engine, but clearly it's better to create a system that can be used is more sensible way. \OMW{} achieves -his with our own content file types. +this with our own content file types. We support both ESM and ESP files, but in order to make use of new features of OpenMW one should consider using new file types designed with our engine in mind: game files and addon files together called ``content files``. @@ -40,7 +40,7 @@ Other simple thing about content files are extensions. We are using .omwaddon fo %TODO describe what content files contains. and what not. \subparagraph{\MW{} content files} Using our content files is recommended solution for projects that are intended to used with \OMW{} engine. However some players -wish to use original Morrowind engine, even with it large flaws and lacking features\footnote{If this is actually wrong, we are very +wish to use original \MW{} engine, even with it large flaws and lacking features\footnote{If this is actually wrong, we are very successful project. Yay!}. Also, since 2002 thousands of ESP/ESM files were created, some with really outstanding content. Because of this \OCS{} simply has no other choice but support ESP/ESM files. However, if you decided to choose ESP/ESM file instead using our own content file types you are most likely aim at the original engine compatibility. This subject is covered in the very @@ -60,7 +60,7 @@ this section of the manual does not cover creating the content files -- it is on keep in mind that dependencies exist, and is up to you what to decide if your content file should depend on other content file. Game files are not intend to have any dependencies for a very simple reasons: player is using only one game file (excluding original -and dirty ESP/ESM system) at the time and therefore no game file can depend on other game file, and since game file makes the base +and dirty {ESP/ESM} system) at the time and therefore no game file can depend on other game file, and since game file makes the base for addon files -- it can not depend on addon files. %\subparagraph{Loading order} %TODO @@ -93,7 +93,7 @@ OpenMW is using {FFmpeg} for audio playback, and so we support every audio type Below is only small portion of supported file types. \begin{description} - \item mp3 (MPEG-1 Part 3 Layer 3) popular audio file format and \textit{de facto} standard for storing audio. Used by the Morrowind game. + \item mp3 ({MPEG}-1 {Part 3 Layer 3}) popular audio file format and \textit{de facto} standard for storing audio. Used by the \MW{} game. \item ogg open source, multimedia container file using high quality vorbis audio codec. Recommended. \end{description} @@ -103,17 +103,18 @@ only the most significant. \begin{description} \item bik videos used by original \MW{} game. - \item mp4 multimedia container which use more advanced codecs (MPEG-4 Parts 2,3,10) with a better audio and video compression rate, - but also requiring more {CPU} intensive decoding -- this makes it probably less suited for video games. + \item mp4 multimedia container which use more advanced codecs ({MPEG-4 Parts 2,3,10}) with a better audio and video compression rate, + but also requiring more {CPU} intensive decoding -- this makes it probably less suited for storing sounds in computer games, but good for videos. \item webm is a new, shiny and open source video format with excellent compression. It needs quite a lot of processing power to be decoded, but since game logic is not running during cut scenes we can recommended it for use with \OMW. \item ogv alternative, open source container using theora codec for video and vorbis for audio. \end{description} \subparagraph{Textures and images} -Original \MW{} game uses DDS and TGA files for all kind of two dimensional images and textures alike. In addition, engine supported BMP -files for some reason (BMP is a terrible format for a video game). We also support extended set of image files -- including JPEG and PNG. +Original \MW{} game uses {DDS} and {TGA} files for all kind of two dimensional images and textures alike. In addition, engine supported BMP +files for some reason ({BMP} is a terrible format for a video game). We also support extended set of image files -- including {JPEG} and {PNG}. JPEG and PNG files can be useful in some cases, for instance JPEG file is a valid option for skybox texture and PNG can useful for masks. -However please, keep in mind that JPEG can grow into large sizes quickly and are not the best option with DirectX rendering backend. +However please, keep in mind that JPEG can grow into large sizes quickly and are not the best option with {DirectX} rendering backend. You probabbly still want +to use {DDS} files for textures. %\subparagraph{Meshes} %TODO once we will support something more than just nifs \ No newline at end of file From 4ef2724f35abb5ebdbb902df943bc3284f4eb65f Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Sun, 1 Dec 2013 13:24:28 +0100 Subject: [PATCH 387/434] Starting dialog window finished. --- manual/opencs/creating_file.tex | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/manual/opencs/creating_file.tex b/manual/opencs/creating_file.tex index 8cab726174..34780a03ed 100644 --- a/manual/opencs/creating_file.tex +++ b/manual/opencs/creating_file.tex @@ -10,8 +10,14 @@ to you with no surprise.\\ First, there is a \textbf{Create A New Game} button. Clearly, you should press it when you want to create a game file. Than, what \textbf{Create A New Addon} button do? Yes! You are right! This button will create any addon content file (and new project file associated with it)! Wonderful! And what the last remaining button do? \textbf{Edit A Content File}? Well, it comes with no surprise that this should be used when you need to alter existing content file, either a game or addon.\\ -\paragraph{Selecting Files} -As We wrote earlier, +\paragraph{Selecting Files For New Addon} +As We wrote earlier, both \OMW{} and \OCS{} are operating with dependency idea in mind. As You remember you should only depend on files you are actually using. But how?\\ +It is simple. When you click either \textbf{Create new Addon} you will be asked to choose those with a new dialog window. The window is using vertical layout, first you should consider the the top element, the one that allows you to select a game file with drop down menu. Since we are operating on the assumption that there is only one game file loaded at the time, you can depend only on one game file. Next, choose addons that you want to use in your addon with checkboxes.\\ + +The last thing to do is to name your your addon and click create. + +\paragraph{Selecting File for Editing} +Clicking \textbf{Edit A Content File} will show somewhat similar window. Here you should select your Game file with drop down menu. If you want to edit this game file, simply click \textbf{OK} button. If you want to alter addon depending on that file, mark it with checkbox and than click \textbf{Ok} button. \subsection{Advanced} If you are paying attention, you noticed any extra icon with wrench. This one will open small settings window. Options here are few, and easy to list. @@ -20,6 +26,4 @@ If you are paying attention, you noticed any extra icon with wrench. This one wi \item {Display format} can be used to configure behavior of the \OCS{} itself, however only in the limited degree. You can decide if you want \OCS{} to display icon, text or both in some columns of tables. \end{description} -%TODO configuration - And that would be it. There is no point spending more time here. We should go forward now. \ No newline at end of file From 2fe135d85f3b73be2554d5ce524591e3d7c1b1bb Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 2 Dec 2013 16:25:26 +0100 Subject: [PATCH 388/434] journals subview was bound to the wrong universal ID type --- apps/opencs/view/world/subviews.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/view/world/subviews.cpp b/apps/opencs/view/world/subviews.cpp index 48c32e171d..74ce03cce6 100644 --- a/apps/opencs/view/world/subviews.cpp +++ b/apps/opencs/view/world/subviews.cpp @@ -58,7 +58,7 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) manager.add (CSMWorld::UniversalId::Type_Topics, new CSVDoc::SubViewFactoryWithCreator); - manager.add (CSMWorld::UniversalId::Type_Journal, + manager.add (CSMWorld::UniversalId::Type_Journals, new CSVDoc::SubViewFactoryWithCreator); manager.add (CSMWorld::UniversalId::Type_TopicInfos, From 890aca720e1fc0796340547948097518b59ddee6 Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Tue, 3 Dec 2013 15:35:03 +0100 Subject: [PATCH 389/434] correcting qoutes style and removing informations about settings from creating_file.tex. Thanks zini. --- manual/opencs/creating_file.tex | 6 +----- manual/opencs/filters.tex | 8 ++++---- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/manual/opencs/creating_file.tex b/manual/opencs/creating_file.tex index 34780a03ed..2c1377ec1e 100644 --- a/manual/opencs/creating_file.tex +++ b/manual/opencs/creating_file.tex @@ -20,10 +20,6 @@ The last thing to do is to name your your addon and click create. Clicking \textbf{Edit A Content File} will show somewhat similar window. Here you should select your Game file with drop down menu. If you want to edit this game file, simply click \textbf{OK} button. If you want to alter addon depending on that file, mark it with checkbox and than click \textbf{Ok} button. \subsection{Advanced} -If you are paying attention, you noticed any extra icon with wrench. This one will open small settings window. Options here are few, and easy to list. -\begin{description} - \item {Window Size} is needed to configure initial window size of the starting window. - \item {Display format} can be used to configure behavior of the \OCS{} itself, however only in the limited degree. You can decide if you want \OCS{} to display icon, text or both in some columns of tables. -\end{description} +If you are paying attention, you noticed any extra icon with wrench. This one will open small settings window. Those are general OpenCS settings. We will cover this is separate section.\\ And that would be it. There is no point spending more time here. We should go forward now. \ No newline at end of file diff --git a/manual/opencs/filters.tex b/manual/opencs/filters.tex index a5f88aebba..36d97e0f5e 100644 --- a/manual/opencs/filters.tex +++ b/manual/opencs/filters.tex @@ -102,7 +102,7 @@ you want to see, while the second one sets desired value inside of the cell. To String in programmers language is often\footnote{Often, not always. There are different programming languages using slightly different terms.} just a word for anything composed of characters. In case of \OCS{} this is in fact true for every value inside the column that is not composed of the pure numbers. Even columns containing only ``true`` and ``false`` values can be targeted by the string expression\footnote{There is no -Boolean (''true'' or ``false'') value in the \OCS. You should use string for those.}. String evaluates to true, +Boolean (``true'' or ``false'') value in the \OCS. You should use string for those.}. String evaluates to true, when record contains in the specified column exactly the same value as specified. Since majority of the columns contain string values, string is among the most often used expressions. Examples: @@ -157,7 +157,7 @@ As you would imagine the range can be specified as including a border value, or The same expression will evaluate to false for value equal 10. \end{itemize} -\paragraph{''true`` and ''false``} +\paragraph{``true'' and ``false''} Nullary \textit{true} and \textit{false} do not accept any arguments, and always evaluates to true (in case of \textit{true}) and false (in case of \textit{false}) no matter what. The main usage of this expressions is the give users ability to quickly disable some part of the filter that makes heavy use of the logical expressions. @@ -184,10 +184,10 @@ This is probably not the most useful filter on earth, but this is not a surprise \mono{or(string(``record type'', npc), string(``record type'', creature))} and will show both npcs and creatures. \paragraph{and -- and(expression1(), expression2())} -\textit{And} is a expression that will return true if all arguments evaluates to true. As in the case of ''or`` you can use two or more arguments, +\textit{And} is a expression that will return true if all arguments evaluates to true. As in the case of ``or'' you can use two or more arguments, separated by a comma. As we mentioned earlier in the \textit{not} filter, combining \textit{not} with \textit{and} can be very useful. For instance to show all armor types, -excluding gauntlets you can write the following: \mono{and (not string("armor type", ".* gauntlet"), string(''Record Type``, ''Armor``))}. +excluding gauntlets you can write the following: \mono{and (not string(``armor type'', ``.* gauntlet''), string(``Record Type'', ``Armor''))}. \subsubsection{Creating and saving filter} In order to create and save new filter, you should go to the filters table, right click and select option ``add record'' from the context menu. From 8f4ffe4ddc569127c5d7a0819798937b95ba11a4 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 3 Dec 2013 15:55:31 +0100 Subject: [PATCH 390/434] Fix new window size not being written to settings when manually resizing window --- apps/openmw/mwrender/renderingmanager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 1b891368ff..26e88fb0dc 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -878,6 +878,8 @@ void RenderingManager::setMenuTransparency(float val) void RenderingManager::windowResized(int x, int y) { + Settings::Manager::setInt("resolution x", "Video", x); + Settings::Manager::setInt("resolution y", "Video", y); mRendering.adjustViewport(); mCompositors->recreate(); From 3a82f8c1934223a9fd10bf93558adc16d360bec1 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 3 Dec 2013 15:56:00 +0100 Subject: [PATCH 391/434] Fix incorrect log file name for launcherOgre.log --- components/ogreinit/ogreinit.cpp | 2 +- libs/openengine/ogre/renderer.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/ogreinit/ogreinit.cpp b/components/ogreinit/ogreinit.cpp index 92a6ed0123..c8fc621c78 100644 --- a/components/ogreinit/ogreinit.cpp +++ b/components/ogreinit/ogreinit.cpp @@ -36,7 +36,7 @@ namespace OgreInit { // Set up logging first new Ogre::LogManager; - Ogre::Log *log = Ogre::LogManager::getSingleton().createLog(logPath + std::string("Ogre.log")); + Ogre::Log *log = Ogre::LogManager::getSingleton().createLog(logPath); // Disable logging to cout/cerr log->setDebugOutputEnabled(false); diff --git a/libs/openengine/ogre/renderer.cpp b/libs/openengine/ogre/renderer.cpp index 2a438e1fef..a0fe6ca849 100644 --- a/libs/openengine/ogre/renderer.cpp +++ b/libs/openengine/ogre/renderer.cpp @@ -50,7 +50,7 @@ void OgreRenderer::configure(const std::string &logPath, const std::string& rttMode ) { - mRoot = mOgreInit.init(logPath); + mRoot = mOgreInit.init(logPath + "/ogre.log"); RenderSystem* rs = mRoot->getRenderSystemByName(renderSystem); if (rs == 0) From 147bc447a5c1e469d3d321ecf442e2b2551fdf53 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 3 Dec 2013 16:07:07 +0100 Subject: [PATCH 392/434] Add specular lighting for directional light to objects shader --- files/materials/objects.mat | 2 +- files/materials/objects.shader | 65 +++++++++++++++++++++++----------- 2 files changed, 45 insertions(+), 22 deletions(-) diff --git a/files/materials/objects.mat b/files/materials/objects.mat index 10fb72740f..ffba6e7ea8 100644 --- a/files/materials/objects.mat +++ b/files/materials/objects.mat @@ -1,7 +1,7 @@ material openmw_objects_base { diffuse 1.0 1.0 1.0 1.0 - specular 0 0 0 0 + specular 0 0 0 0 1 ambient 1.0 1.0 1.0 emissive 0.0 0.0 0.0 vertmode 0 diff --git a/files/materials/objects.shader b/files/materials/objects.shader index 9fc9fceaf0..4acc23b79d 100644 --- a/files/materials/objects.shader +++ b/files/materials/objects.shader @@ -32,6 +32,10 @@ #define ENV_MAP @shPropertyBool(env_map) +#define SPECULAR 1 + +#define NEED_NORMAL (!VERTEX_LIGHTING || ENV_MAP) || SPECULAR + #ifdef SH_VERTEX_SHADER // ------------------------------------- VERTEX --------------------------------------- @@ -63,15 +67,12 @@ shOutput(float3, tangentPassthrough) #endif -#if !VERTEX_LIGHTING || ENV_MAP +#if NEED_NORMAL shOutput(float3, normalPassthrough) #endif -#ifdef NEED_DEPTH - shOutput(float, depthPassthrough) -#endif - - shOutput(float3, objSpacePositionPassthrough) + // Depth in w + shOutput(float4, objSpacePositionPassthrough) #if VERTEXCOLOR_MODE != 0 shColourInput(float4) @@ -146,7 +147,7 @@ #if NORMAL_MAP tangentPassthrough = tangent.xyz; #endif -#if !VERTEX_LIGHTING || ENV_MAP +#if NEED_NORMAL normalPassthrough = normal.xyz; #endif #if VERTEXCOLOR_MODE != 0 && !VERTEX_LIGHTING @@ -169,14 +170,14 @@ float4x4 fixedWVP = shMatrixMult(vpFixed, worldMatrix); - depthPassthrough = shMatrixMult(fixedWVP, shInputPosition).z; + objSpacePositionPassthrough.w = shMatrixMult(fixedWVP, shInputPosition).z; #else - depthPassthrough = shOutputPosition.z; + objSpacePositionPassthrough.w = shOutputPosition.z; #endif #endif - objSpacePositionPassthrough = shInputPosition.xyz; + objSpacePositionPassthrough.xyz = shInputPosition.xyz; #if SHADOWS lightSpacePos0 = shMatrixMult(texViewProjMatrix0, shMatrixMult(worldMatrix, shInputPosition)); @@ -262,7 +263,16 @@ #if ENV_MAP shSampler2D(envMap) shUniform(float3, env_map_color) @shUniformProperty3f(env_map_color, env_map_color) - shUniform(float3, cameraPosObjSpace) @shAutoConstant(cameraPosObjSpace, camera_position_object_space) +#endif + +#if ENV_MAP || SPECULAR + shUniform(float3, cameraPosObjSpace) @shAutoConstant(cameraPosObjSpace, camera_position_object_space) +#endif +#if SPECULAR + shUniform(float3, lightSpec0) @shAutoConstant(lightSpec0, light_specular_colour, 0) + shUniform(float3, lightPosObjSpace0) @shAutoConstant(lightPosObjSpace0, light_position_object_space, 0) + shUniform(float, matShininess) @shAutoConstant(matShininess, surface_shininess) + shUniform(float3, matSpec) @shAutoConstant(matSpec, surface_specular_colour) #endif shInput(float4, UV) @@ -270,15 +280,11 @@ #if NORMAL_MAP shInput(float3, tangentPassthrough) #endif -#if !VERTEX_LIGHTING || ENV_MAP +#if NEED_NORMAL shInput(float3, normalPassthrough) #endif -#ifdef NEED_DEPTH - shInput(float, depthPassthrough) -#endif - - shInput(float3, objSpacePositionPassthrough) + shInput(float4, objSpacePositionPassthrough) #if VERTEXCOLOR_MODE != 0 && !VERTEX_LIGHTING shInput(float4, colourPassthrough) @@ -340,6 +346,10 @@ SH_START_PROGRAM { +#ifdef NEED_DEPTH + float depthPassthrough = objSpacePositionPassthrough.w; +#endif + shOutputColour(0) = shSample(diffuseMap, UV.xy); #if DETAIL_MAP @@ -350,7 +360,7 @@ #endif #endif -#if !VERTEX_LIGHTING || ENV_MAP +#if NEED_NORMAL float3 normal = normalPassthrough; #endif @@ -368,7 +378,7 @@ #endif #if !VERTEX_LIGHTING - float3 viewPos = shMatrixMult(worldView, float4(objSpacePositionPassthrough,1)).xyz; + float3 viewPos = shMatrixMult(worldView, float4(objSpacePositionPassthrough.xyz,1)).xyz; float3 viewNormal = normalize(shMatrixMult(worldView, float4(normal.xyz, 0)).xyz); float3 lightDir; @@ -433,7 +443,7 @@ #if (UNDERWATER) || (FOG) - float3 worldPos = shMatrixMult(worldMatrix, float4(objSpacePositionPassthrough,1)).xyz; + float3 worldPos = shMatrixMult(worldMatrix, float4(objSpacePositionPassthrough.xyz,1)).xyz; #endif #if UNDERWATER @@ -454,15 +464,28 @@ #endif #endif +#if ENV_MAP || SPECULAR + float3 eyeDir = normalize(cameraPosObjSpace.xyz - objSpacePositionPassthrough.xyz); +#endif + #if ENV_MAP // Everything looks better with fresnel - float3 eyeDir = normalize(cameraPosObjSpace.xyz - objSpacePositionPassthrough.xyz); float facing = 1.0 - max(abs(dot(-eyeDir, normal)), 0); float envFactor = shSaturate(0.25 + 0.75 * pow(facing, 1)); shOutputColour(0).xyz += shSample(envMap, UV.zw).xyz * envFactor * env_map_color; #endif +#if SPECULAR + float3 light0Dir = normalize(lightPosObjSpace0.xyz); + + float NdotL = max(dot(normal, light0Dir), 0); + float3 halfVec = normalize (light0Dir + eyeDir); + + float3 specular = pow(max(dot(normal, halfVec), 0), matShininess) * lightSpec0 * matSpec; + shOutputColour(0).xyz += specular * shadow; +#endif + #if FOG float fogValue = shSaturate((depthPassthrough - fogParams.y) * fogParams.w); From 5f1878eb54257d54e9507d3df7d664e96b501f71 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 3 Dec 2013 18:41:45 +0100 Subject: [PATCH 393/434] Add specular mapping (uses alpha channel of diffuse texture) --- files/materials/objects.shader | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/files/materials/objects.shader b/files/materials/objects.shader index 4acc23b79d..20abe5b7ce 100644 --- a/files/materials/objects.shader +++ b/files/materials/objects.shader @@ -350,7 +350,8 @@ float depthPassthrough = objSpacePositionPassthrough.w; #endif - shOutputColour(0) = shSample(diffuseMap, UV.xy); + float4 diffuse = shSample(diffuseMap, UV.xy); + shOutputColour(0) = diffuse; #if DETAIL_MAP #if @shPropertyString(detailMapUVSet) @@ -483,7 +484,7 @@ float3 halfVec = normalize (light0Dir + eyeDir); float3 specular = pow(max(dot(normal, halfVec), 0), matShininess) * lightSpec0 * matSpec; - shOutputColour(0).xyz += specular * shadow; + shOutputColour(0).xyz += specular * shadow * diffuse.a; #endif #if FOG From 47c60a703767c407e21f4ce7e0e06f6770afa12f Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Wed, 4 Dec 2013 21:48:25 +0100 Subject: [PATCH 394/434] Fix C4716 (Must return a value) error on Windows MSVC --- components/nifogre/particles.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/nifogre/particles.cpp b/components/nifogre/particles.cpp index 006a570dce..d902387b9a 100644 --- a/components/nifogre/particles.cpp +++ b/components/nifogre/particles.cpp @@ -27,6 +27,7 @@ public: Ogre::String doGet(const void *target) const { assert(false && "Unimplemented"); + return ""; } void doSet(void *target, const Ogre::String &val) { @@ -41,6 +42,7 @@ public: Ogre::String doGet(const void *target) const { assert(false && "Unimplemented"); + return ""; } void doSet(void *target, const Ogre::String &val) { @@ -563,6 +565,7 @@ public: Ogre::String doGet(const void *target) const { assert(false && "Unimplemented"); + return ""; } void doSet(void *target, const Ogre::String &val) { @@ -577,6 +580,7 @@ public: Ogre::String doGet(const void *target) const { assert(false && "Unimplemented"); + return ""; } void doSet(void *target, const Ogre::String &val) { From f695deb29dc01c33433502bfa3c9c50ca520da51 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 3 Dec 2013 18:42:35 +0100 Subject: [PATCH 395/434] Remember window positions --- apps/openmw/mwgui/inventorywindow.cpp | 67 +++++++++++++++++--------- apps/openmw/mwgui/inventorywindow.hpp | 7 +-- apps/openmw/mwgui/messagebox.cpp | 4 -- apps/openmw/mwgui/statswindow.hpp | 2 + apps/openmw/mwgui/windowmanagerimp.cpp | 51 ++++++++++++++++++++ apps/openmw/mwgui/windowmanagerimp.hpp | 5 ++ files/mygui/openmw_stats_window.layout | 2 +- files/settings-default.cfg | 66 +++++++++++++++++++++++++ libs/openengine/gui/layout.hpp | 2 +- 9 files changed, 172 insertions(+), 34 deletions(-) diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 022e0a47d8..56c474c89a 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -37,10 +37,6 @@ namespace MWGui , mPreviewDirty(true) , mDragAndDrop(dragAndDrop) , mSelectedItem(-1) - , mPositionInventory(0, 342, 498, 258) - , mPositionContainer(0, 342, 498, 258) - , mPositionCompanion(0, 342, 498, 258) - , mPositionBarter(0, 342, 498, 258) , mGuiMode(GM_Inventory) { static_cast(mMainWidget)->eventWindowChangeCoord += MyGUI::newDelegate(this, &InventoryWindow::onWindowResize); @@ -71,8 +67,19 @@ namespace MWGui mFilterAll->setStateSelected(true); - setCoord(mPositionInventory.left, mPositionInventory.top, mPositionInventory.width, mPositionInventory.height); - onWindowResize(static_cast(mMainWidget)); + setGuiMode(mGuiMode); + + adjustPanes(); + } + + void InventoryWindow::adjustPanes() + { + const float aspect = 0.5; // fixed aspect ratio for the left pane + mLeftPane->setSize( (mMainWidget->getSize().height-44) * aspect, mMainWidget->getSize().height-44 ); + mRightPane->setCoord( mLeftPane->getPosition().left + (mMainWidget->getSize().height-44) * aspect + 4, + mRightPane->getPosition().top, + mMainWidget->getSize().width - 12 - (mMainWidget->getSize().height-44) * aspect - 15, + mMainWidget->getSize().height-44 ); } void InventoryWindow::updatePlayer() @@ -87,27 +94,36 @@ namespace MWGui void InventoryWindow::setGuiMode(GuiMode mode) { + std::string setting = "inventory"; mGuiMode = mode; switch(mode) { case GM_Container: setPinButtonVisible(false); - mMainWidget->setCoord(mPositionContainer); + setting += " container"; break; case GM_Companion: setPinButtonVisible(false); - mMainWidget->setCoord(mPositionCompanion); + setting += " companion"; break; case GM_Barter: setPinButtonVisible(false); - mMainWidget->setCoord(mPositionBarter); + setting += " barter"; break; case GM_Inventory: default: setPinButtonVisible(true); - mMainWidget->setCoord(mPositionInventory); break; } - onWindowResize(static_cast(mMainWidget)); + + MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); + MyGUI::IntPoint pos (Settings::Manager::getFloat(setting + " x", "Windows") * viewSize.width, + Settings::Manager::getFloat(setting + " y", "Windows") * viewSize.height); + MyGUI::IntSize size (Settings::Manager::getFloat(setting + " w", "Windows") * viewSize.width, + Settings::Manager::getFloat(setting + " h", "Windows") * viewSize.height); + mMainWidget->setPosition(pos); + mMainWidget->setSize(size); + adjustPanes(); + mPreviewDirty = true; } TradeItemModel* InventoryWindow::getTradeModel() @@ -256,32 +272,37 @@ namespace MWGui mItemView->update(); notifyContentChanged(); + adjustPanes(); } void InventoryWindow::onWindowResize(MyGUI::Window* _sender) { - const float aspect = 0.5; // fixed aspect ratio for the left pane - mLeftPane->setSize( (_sender->getSize().height-44) * aspect, _sender->getSize().height-44 ); - mRightPane->setCoord( mLeftPane->getPosition().left + (_sender->getSize().height-44) * aspect + 4, - mRightPane->getPosition().top, - _sender->getSize().width - 12 - (_sender->getSize().height-44) * aspect - 15, - _sender->getSize().height-44 ); - + adjustPanes(); + std::string setting = "inventory"; switch(mGuiMode) { case GM_Container: - mPositionContainer = _sender->getCoord(); + setting += " container"; break; case GM_Companion: - mPositionCompanion = _sender->getCoord(); + setting += " companion"; break; case GM_Barter: - mPositionBarter = _sender->getCoord(); + setting += " barter"; break; - case GM_Inventory: default: - mPositionInventory = _sender->getCoord(); + break; } + MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); + float x = _sender->getPosition().left / float(viewSize.width); + float y = _sender->getPosition().top / float(viewSize.height); + float w = _sender->getSize().width / float(viewSize.width); + float h = _sender->getSize().height / float(viewSize.height); + Settings::Manager::setFloat(setting + " x", "Windows", x); + Settings::Manager::setFloat(setting + " y", "Windows", y); + Settings::Manager::setFloat(setting + " w", "Windows", w); + Settings::Manager::setFloat(setting + " h", "Windows", h); + if (mMainWidget->getSize().width != mLastXSize || mMainWidget->getSize().height != mLastYSize) { mLastXSize = mMainWidget->getSize().width; diff --git a/apps/openmw/mwgui/inventorywindow.hpp b/apps/openmw/mwgui/inventorywindow.hpp index 78d00fd1e7..94ecfd4c8a 100644 --- a/apps/openmw/mwgui/inventorywindow.hpp +++ b/apps/openmw/mwgui/inventorywindow.hpp @@ -76,11 +76,6 @@ namespace MWGui MyGUI::Button* mFilterMagic; MyGUI::Button* mFilterMisc; - MyGUI::IntCoord mPositionInventory; - MyGUI::IntCoord mPositionContainer; - MyGUI::IntCoord mPositionCompanion; - MyGUI::IntCoord mPositionBarter; - GuiMode mGuiMode; int mLastXSize; @@ -105,6 +100,8 @@ namespace MWGui void updateEncumbranceBar(); void notifyContentChanged(); + + void adjustPanes(); }; } diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index 0a9ee01888..8486218f0b 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -154,10 +154,6 @@ namespace MWGui size.width = mFixedWidth; size.height = 100; // dummy - MyGUI::IntCoord coord; - coord.left = 10; // dummy - coord.top = 10; // dummy - mMessageWidget->setSize(size); MyGUI::IntSize textSize = mMessageWidget->getTextSize(); diff --git a/apps/openmw/mwgui/statswindow.hpp b/apps/openmw/mwgui/statswindow.hpp index ac8319bdcd..49b44a2e16 100644 --- a/apps/openmw/mwgui/statswindow.hpp +++ b/apps/openmw/mwgui/statswindow.hpp @@ -37,6 +37,8 @@ namespace MWGui void setBounty (int bounty) { if (bounty != mBounty) mChanged = true; this->mBounty = bounty; } void updateSkillArea(); + virtual void open() { onWindowResize(static_cast(mMainWidget)); } + private: void addSkills(const SkillList &skills, const std::string &titleId, const std::string &titleDefault, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); void addSeparator(MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 2bcfdfb62d..78986a0522 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -203,16 +203,22 @@ namespace MWGui mRecharge = new Recharge(); mMenu = new MainMenu(w,h); mMap = new MapWindow(""); + trackWindow(mMap, "map"); mStatsWindow = new StatsWindow(); + trackWindow(mStatsWindow, "stats"); mConsole = new Console(w,h, mConsoleOnlyScripts); + trackWindow(mConsole, "console"); mJournal = JournalWindow::create(JournalViewModel::create ()); mMessageBoxManager = new MessageBoxManager(); mInventoryWindow = new InventoryWindow(mDragAndDrop); mTradeWindow = new TradeWindow(); + trackWindow(mTradeWindow, "barter"); mSpellBuyingWindow = new SpellBuyingWindow(); mTravelWindow = new TravelWindow(); mDialogueWindow = new DialogueWindow(); + trackWindow(mDialogueWindow, "dialogue"); mContainerWindow = new ContainerWindow(mDragAndDrop); + trackWindow(mContainerWindow, "container"); mHud = new HUD(w,h, mShowFPSLevel, mDragAndDrop); mToolTips = new ToolTips(); mScrollWindow = new ScrollWindow(); @@ -221,7 +227,9 @@ namespace MWGui mSettingsWindow = new SettingsWindow(); mConfirmationDialog = new ConfirmationDialog(); mAlchemyWindow = new AlchemyWindow(); + trackWindow(mAlchemyWindow, "alchemy"); mSpellWindow = new SpellWindow(); + trackWindow(mSpellWindow, "spells"); mQuickKeysMenu = new QuickKeysMenu(); mLevelupDialog = new LevelupDialog(); mWaitDialog = new WaitDialog(); @@ -232,6 +240,7 @@ namespace MWGui mRepair = new Repair(); mSoulgemDialog = new SoulgemDialog(mMessageBoxManager); mCompanionWindow = new CompanionWindow(mDragAndDrop, mMessageBoxManager); + trackWindow(mCompanionWindow, "companion"); mInputBlocker = mGui->createWidget("",0,0,w,h,MyGUI::Align::Default,"Windows",""); @@ -926,6 +935,17 @@ namespace MWGui mLoadingScreen->onResChange (x,y); if (!mHud) return; // UI not initialized yet + + for (std::map::iterator it = mTrackedWindows.begin(); it != mTrackedWindows.end(); ++it) + { + MyGUI::IntPoint pos (Settings::Manager::getFloat(it->second + " x", "Windows") * x, + Settings::Manager::getFloat(it->second+ " y", "Windows") * y); + MyGUI::IntSize size (Settings::Manager::getFloat(it->second + " w", "Windows") * x, + Settings::Manager::getFloat(it->second + " h", "Windows") * y); + it->first->setPosition(pos); + it->first->setSize(size); + } + mHud->onResChange(x, y); mConsole->onResChange(x, y); mMenu->onResChange(x, y); @@ -1364,4 +1384,35 @@ namespace MWGui return mCursorVisible; } + void WindowManager::trackWindow(OEngine::GUI::Layout *layout, const std::string &name) + { + MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); + MyGUI::IntPoint pos (Settings::Manager::getFloat(name + " x", "Windows") * viewSize.width, + Settings::Manager::getFloat(name + " y", "Windows") * viewSize.height); + MyGUI::IntSize size (Settings::Manager::getFloat(name + " w", "Windows") * viewSize.width, + Settings::Manager::getFloat(name + " h", "Windows") * viewSize.height); + layout->mMainWidget->setPosition(pos); + layout->mMainWidget->setSize(size); + + MyGUI::Window* window = dynamic_cast(layout->mMainWidget); + if (!window) + throw std::runtime_error("Attempting to track size of a non-resizable window"); + window->eventWindowChangeCoord += MyGUI::newDelegate(this, &WindowManager::onWindowChangeCoord); + mTrackedWindows[window] = name; + } + + void WindowManager::onWindowChangeCoord(MyGUI::Window *_sender) + { + std::string setting = mTrackedWindows[_sender]; + MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); + float x = _sender->getPosition().left / float(viewSize.width); + float y = _sender->getPosition().top / float(viewSize.height); + float w = _sender->getSize().width / float(viewSize.width); + float h = _sender->getSize().height / float(viewSize.height); + Settings::Manager::setFloat(setting + " x", "Windows", x); + Settings::Manager::setFloat(setting + " y", "Windows", y); + Settings::Manager::setFloat(setting + " w", "Windows", w); + Settings::Manager::setFloat(setting + " h", "Windows", h); + } + } diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index c5b6ff2beb..4f19602958 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -16,6 +16,7 @@ namespace MyGUI { class Gui; class Widget; + class Window; class UString; } @@ -283,6 +284,10 @@ namespace MWGui private: bool mConsoleOnlyScripts; + std::map mTrackedWindows; + void trackWindow(OEngine::GUI::Layout* layout, const std::string& name); + void onWindowChangeCoord(MyGUI::Window* _sender); + OEngine::GUI::MyGUIManager *mGuiManager; OEngine::Render::OgreRenderer *mRendering; HUD *mHud; diff --git a/files/mygui/openmw_stats_window.layout b/files/mygui/openmw_stats_window.layout index 5ae3f96caf..36c28c450e 100644 --- a/files/mygui/openmw_stats_window.layout +++ b/files/mygui/openmw_stats_window.layout @@ -2,7 +2,7 @@ - + diff --git a/files/settings-default.cfg b/files/settings-default.cfg index f191430df1..94bdd8c9df 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -169,3 +169,69 @@ ui y multiplier = 1.0 [Game] # Always use the most powerful attack when striking with a weapon (chop, slash or thrust) best attack = false + +[Windows] +inventory x = 0 +inventory y = 0.4275 +inventory w = 0.6225 +inventory h = 0.5725 + +inventory container x = 0 +inventory container y = 0.4275 +inventory container w = 0.6225 +inventory container h = 0.5725 + +inventory barter x = 0 +inventory barter y = 0.4275 +inventory barter w = 0.6225 +inventory barter h = 0.5725 + +inventory companion x = 0 +inventory companion y = 0.4275 +inventory companion w = 0.6225 +inventory companion h = 0.5725 + +container x = 0.25 +container y = 0 +container w = 0.75 +container h = 0.375 + +companion x = 0.25 +companion y = 0 +companion w = 0.75 +companion h = 0.375 + +map x = 0.625 +map y = 0 +map w = 0.375 +map h = 0.5725 + +barter x = 0.25 +barter y = 0 +barter w = 0.75 +barter h = 0.375 + +alchemy x = 0.25 +alchemy y = 0.25 +alchemy w = 0.5 +alchemy h = 0.5 + +stats x = 0 +stats y = 0 +stats w = 0.375 +stats h = 0.4275 + +spells x = 0.3775 +spells y = 0.4275 +spells w = 0.375 +spells h = 0.5725 + +console x = 0 +console y = 0 +console w = 1 +console h = 0.5 + +dialogue h = 0.810 +dialogue w = 0.810 +dialogue x = 0.095 +dialogue y = 0.095 diff --git a/libs/openengine/gui/layout.hpp b/libs/openengine/gui/layout.hpp index 9040dfb90e..26a3fdab84 100644 --- a/libs/openengine/gui/layout.hpp +++ b/libs/openengine/gui/layout.hpp @@ -150,10 +150,10 @@ namespace GUI MyGUI::IntSize size = button->getTextSize(); button->setSize(size.width + 24, button->getSize().height); } + MyGUI::Widget* mMainWidget; protected: - MyGUI::Widget* mMainWidget; std::string mPrefix; std::string mLayoutName; MyGUI::VectorWidgetPtr mListWindowRoot; From 9ab8fe10388bfda90aef06c762095d48b97ed830 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 4 Dec 2013 16:03:32 +0100 Subject: [PATCH 396/434] Fix bsatool warning --- apps/bsatool/bsatool.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/bsatool/bsatool.cpp b/apps/bsatool/bsatool.cpp index e6fcc2567d..3781dd066e 100644 --- a/apps/bsatool/bsatool.cpp +++ b/apps/bsatool/bsatool.cpp @@ -184,7 +184,7 @@ int list(Bsa::BSAFile& bsa, Arguments& info) { // List all files const Bsa::BSAFile::FileList &files = bsa.getList(); - for(int i=0; i Date: Wed, 4 Dec 2013 21:43:10 +0100 Subject: [PATCH 397/434] Add normal, specular & parallax mapping for terrain --- components/terrain/material.cpp | 53 +++++++++- components/terrain/material.hpp | 10 +- components/terrain/quadtreenode.cpp | 12 ++- components/terrain/storage.cpp | 36 ++++++- components/terrain/storage.hpp | 13 ++- files/materials/objects.shader | 1 - files/materials/terrain.shader | 155 +++++++++++++++++++++++++--- 7 files changed, 251 insertions(+), 29 deletions(-) diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index ebf6046ff6..b421de5a28 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -4,6 +4,8 @@ #include #include +#include + #include namespace @@ -36,6 +38,8 @@ namespace Terrain : mShaders(shaders) , mShadows(false) , mSplitShadows(false) + , mNormalMapping(true) + , mParallaxMapping(true) { } @@ -85,7 +89,7 @@ namespace Terrain { assert(mLayerList.size() == mBlendmapList.size()+1); std::vector::iterator blend = mBlendmapList.begin(); - for (std::vector::iterator layer = mLayerList.begin(); layer != mLayerList.end(); ++layer) + for (std::vector::iterator layer = mLayerList.begin(); layer != mLayerList.end(); ++layer) { Ogre::Pass* pass = technique->createPass(); pass->setLightingEnabled(false); @@ -117,7 +121,7 @@ namespace Terrain } // Add the actual layer texture on top of the alpha map. - tus = pass->createTextureUnitState("textures\\" + *layer); + tus = pass->createTextureUnitState(layer->mDiffuseMap); if (!first) tus->setColourOperationEx(Ogre::LBX_BLEND_DIFFUSE_ALPHA, Ogre::LBS_TEXTURE, @@ -156,6 +160,10 @@ namespace Terrain p->mShaderProperties.setProperty ("display_composite_map", sh::makeProperty(new sh::BooleanValue(true))); p->mShaderProperties.setProperty ("num_layers", sh::makeProperty (new sh::StringValue("0"))); p->mShaderProperties.setProperty ("num_blendmaps", sh::makeProperty (new sh::StringValue("0"))); + p->mShaderProperties.setProperty ("normal_map_enabled", sh::makeProperty (new sh::BooleanValue(false))); + p->mShaderProperties.setProperty ("parallax_enabled", sh::makeProperty (new sh::BooleanValue(false))); + p->mShaderProperties.setProperty ("normal_maps", + sh::makeProperty (new sh::IntValue(0))); sh::MaterialInstanceTextureUnit* tex = p->createTextureUnit ("compositeMap"); tex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(mCompositeMap))); @@ -210,6 +218,11 @@ namespace Terrain } } ++neededTextureUnits; // layer texture + + // Check if this layer has a normal map + if (mNormalMapping && !mLayerList[layerOffset].mNormalMap.empty()) + ++neededTextureUnits; // normal map + if (neededTextureUnits <= remainingTextureUnits) { // We can fit another! @@ -238,6 +251,8 @@ namespace Terrain p->mShaderProperties.setProperty ("num_layers", sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(numLayersInThisPass)))); p->mShaderProperties.setProperty ("num_blendmaps", sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(numBlendTextures)))); + p->mShaderProperties.setProperty ("normal_map_enabled", + sh::makeProperty (new sh::BooleanValue(false))); // blend maps // the index of the first blend map used in this pass @@ -254,17 +269,39 @@ namespace Terrain } // layer maps + bool anyNormalMaps = false; + bool anyParallax = false; + size_t normalMaps = 0; for (int i = 0; i < numLayersInThisPass; ++i) { + const LayerInfo& layer = mLayerList[layerOffset+i]; + // diffuse map sh::MaterialInstanceTextureUnit* diffuseTex = p->createTextureUnit ("diffuseMap" + Ogre::StringConverter::toString(i)); - diffuseTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue("textures\\"+mLayerList[layerOffset+i]))); + diffuseTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(layer.mDiffuseMap))); + + // normal map (optional) + bool useNormalMap = mNormalMapping && !mLayerList[layerOffset+i].mNormalMap.empty() && !renderCompositeMap; + bool useParallax = useNormalMap && mParallaxMapping && layer.mParallax; + if (useNormalMap) + { + anyNormalMaps = true; + anyParallax = anyParallax || useParallax; + sh::MaterialInstanceTextureUnit* normalTex = p->createTextureUnit ("normalMap" + Ogre::StringConverter::toString(i)); + normalTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(layer.mNormalMap))); + } + p->mShaderProperties.setProperty ("use_normal_map_" + Ogre::StringConverter::toString(i), + sh::makeProperty (new sh::BooleanValue(useNormalMap))); + p->mShaderProperties.setProperty ("use_parallax_" + Ogre::StringConverter::toString(i), + sh::makeProperty (new sh::BooleanValue(useParallax))); + boost::hash_combine(normalMaps, useNormalMap); + boost::hash_combine(normalMaps, useNormalMap && layer.mParallax); if (i+layerOffset > 0) { int blendTextureIndex = getBlendmapIndexForLayer(layerOffset+i); std::string blendTextureComponent = getBlendmapComponentForLayer(layerOffset+i); p->mShaderProperties.setProperty ("blendmap_component_" + Ogre::StringConverter::toString(i), - sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(blendTextureIndex-blendmapStart) + "." + blendTextureComponent))); + sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(blendTextureIndex-blendmapStart) + "." + blendTextureComponent))); } else { @@ -274,6 +311,14 @@ namespace Terrain sh::makeProperty (new sh::StringValue(""))); } } + p->mShaderProperties.setProperty ("normal_map_enabled", + sh::makeProperty (new sh::BooleanValue(anyNormalMaps))); + p->mShaderProperties.setProperty ("parallax_enabled", + sh::makeProperty (new sh::BooleanValue(anyParallax))); + // Since the permutation handler can't handle dynamic property names, + // combine normal map settings for all layers into one value + p->mShaderProperties.setProperty ("normal_maps", + sh::makeProperty (new sh::IntValue(normalMaps))); // shadow if (shadows) diff --git a/components/terrain/material.hpp b/components/terrain/material.hpp index 330ed3d147..e7e0678991 100644 --- a/components/terrain/material.hpp +++ b/components/terrain/material.hpp @@ -3,6 +3,8 @@ #include +#include "storage.hpp" + namespace Terrain { @@ -15,13 +17,15 @@ namespace Terrain /// so if this parameter is true, then the supplied blend maps are expected to be packed. MaterialGenerator (bool shaders); - void setLayerList (const std::vector& layerList) { mLayerList = layerList; } + void setLayerList (const std::vector& layerList) { mLayerList = layerList; } bool hasLayers() { return mLayerList.size(); } void setBlendmapList (const std::vector& blendmapList) { mBlendmapList = blendmapList; } const std::vector& getBlendmapList() { return mBlendmapList; } void setCompositeMap (const std::string& name) { mCompositeMap = name; } void enableShadows(bool shadows) { mShadows = shadows; } + void enableNormalMapping(bool normalMapping) { mNormalMapping = normalMapping; } + void enableParallaxMapping(bool parallaxMapping) { mParallaxMapping = parallaxMapping; } void enableSplitShadows(bool splitShadows) { mSplitShadows = splitShadows; } /// Creates a material suitable for displaying a chunk of terrain using alpha-blending. @@ -43,12 +47,14 @@ namespace Terrain private: Ogre::MaterialPtr create (Ogre::MaterialPtr mat, bool renderCompositeMap, bool displayCompositeMap); - std::vector mLayerList; + std::vector mLayerList; std::vector mBlendmapList; std::string mCompositeMap; bool mShaders; bool mShadows; bool mSplitShadows; + bool mNormalMapping; + bool mParallaxMapping; }; } diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp index ef2c610138..e16ae55ddb 100644 --- a/components/terrain/quadtreenode.cpp +++ b/components/terrain/quadtreenode.cpp @@ -371,7 +371,7 @@ void QuadTreeNode::destroyChunks(bool children) for (std::vector::const_iterator it = list.begin(); it != list.end(); ++it) Ogre::TextureManager::getSingleton().remove((*it)->getName()); mMaterialGenerator->setBlendmapList(std::vector()); - mMaterialGenerator->setLayerList(std::vector()); + mMaterialGenerator->setLayerList(std::vector()); mMaterialGenerator->setCompositeMap(""); } @@ -414,7 +414,7 @@ void QuadTreeNode::ensureLayerInfo() return; std::vector blendmaps; - std::vector layerList; + std::vector layerList; mTerrain->getStorage()->getBlendmaps(mSize, mCenter, mTerrain->getShadersEnabled(), blendmaps, layerList); mMaterialGenerator->setLayerList(layerList); @@ -427,11 +427,13 @@ void QuadTreeNode::prepareForCompositeMap(Ogre::TRect area) if (mIsDummy) { - // TODO - why is this completely black? // TODO - store this default material somewhere instead of creating one for each empty cell MaterialGenerator matGen(mTerrain->getShadersEnabled()); - std::vector layer; - layer.push_back("_land_default.dds"); + std::vector layer; + LayerInfo info; + info.mDiffuseMap = "textures\\_land_default.dds"; + info.mParallax = false; + layer.push_back(info); matGen.setLayerList(layer); makeQuad(sceneMgr, area.left, area.top, area.right, area.bottom, matGen.generateForCompositeMapRTT(Ogre::MaterialPtr())); return; diff --git a/components/terrain/storage.cpp b/components/terrain/storage.cpp index f00677e97f..9d6b44de8d 100644 --- a/components/terrain/storage.cpp +++ b/components/terrain/storage.cpp @@ -4,9 +4,10 @@ #include #include #include +#include #include -#include +#include namespace Terrain { @@ -302,7 +303,7 @@ namespace Terrain } void Storage::getBlendmaps(float chunkSize, const Ogre::Vector2 &chunkCenter, - bool pack, std::vector &blendmaps, std::vector &layerList) + bool pack, std::vector &blendmaps, std::vector &layerList) { // TODO - blending isn't completely right yet; the blending radius appears to be // different at a cell transition (2 vertices, not 4), so we may need to create a larger blendmap @@ -336,7 +337,7 @@ namespace Terrain { int size = textureIndicesMap.size(); textureIndicesMap[*it] = size; - layerList.push_back(getTextureName(*it)); + layerList.push_back(getLayerInfo(getTextureName(*it))); } int numTextures = textureIndices.size(); @@ -466,5 +467,34 @@ namespace Terrain return land->mLandData->mHeights[y * ESM::Land::LAND_SIZE + x]; } + LayerInfo Storage::getLayerInfo(const std::string& texture) + { + // Already have this cached? + if (mLayerInfoMap.find(texture) != mLayerInfoMap.end()) + return mLayerInfoMap[texture]; + + LayerInfo info; + info.mParallax = false; + info.mDiffuseMap = "textures\\" + texture; + std::string texture_ = texture; + boost::replace_last(texture_, ".", "_nh."); + if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("textures\\" + texture_)) + { + info.mNormalMap = "textures\\" + texture_; + info.mParallax = true; + } + else + { + texture_ = texture; + boost::replace_last(texture_, ".", "_n."); + if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("textures\\" + texture_)) + info.mNormalMap = "textures\\" + texture_; + } + + mLayerInfoMap[texture] = info; + + return info; + } + } diff --git a/components/terrain/storage.hpp b/components/terrain/storage.hpp index b82f6bbb62..68fae01afa 100644 --- a/components/terrain/storage.hpp +++ b/components/terrain/storage.hpp @@ -11,6 +11,13 @@ namespace Terrain { + struct LayerInfo + { + std::string mDiffuseMap; + std::string mNormalMap; + bool mParallax; // Height info in normal map alpha channel? + }; + /// We keep storage of terrain data abstract here since we need different implementations for game and editor class Storage { @@ -58,7 +65,7 @@ namespace Terrain /// @param layerList names of the layer textures used will be written here void getBlendmaps (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack, std::vector& blendmaps, - std::vector& layerList); + std::vector& layerList); float getHeightAt (const Ogre::Vector3& worldPos); @@ -77,6 +84,10 @@ namespace Terrain UniqueTextureId getVtexIndexAt(int cellX, int cellY, int x, int y); std::string getTextureName (UniqueTextureId id); + + std::map mLayerInfoMap; + + LayerInfo getLayerInfo(const std::string& texture); }; } diff --git a/files/materials/objects.shader b/files/materials/objects.shader index 20abe5b7ce..fb2b304262 100644 --- a/files/materials/objects.shader +++ b/files/materials/objects.shader @@ -327,7 +327,6 @@ shInput(float4, lightResult) shInput(float3, directionalResult) #else - shUniform(float, lightCount) @shAutoConstant(lightCount, light_count) shUniform(float4, lightPosition[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightPosition, light_position_view_space_array, @shGlobalSettingString(num_lights)) shUniform(float4, lightDiffuse[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightDiffuse, light_diffuse_colour_array, @shGlobalSettingString(num_lights)) shUniform(float4, lightAttenuation[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightAttenuation, light_attenuation_array, @shGlobalSettingString(num_lights)) diff --git a/files/materials/terrain.shader b/files/materials/terrain.shader index 861841a84c..eda80c9e30 100644 --- a/files/materials/terrain.shader +++ b/files/materials/terrain.shader @@ -27,6 +27,16 @@ #define COMPOSITE_MAP @shPropertyBool(display_composite_map) +#define NORMAL_MAP @shPropertyBool(normal_map_enabled) +#define PARALLAX @shPropertyBool(parallax_enabled) + +#define VERTEX_LIGHTING (!NORMAL_MAP) + +#define PARALLAX_SCALE 0.04 +#define PARALLAX_BIAS -0.02 + +// This is just for the permutation handler +#define NORMAL_MAPS @shPropertyString(normal_maps) #if NEED_DEPTH @shAllocatePassthrough(1, depth) @@ -37,8 +47,13 @@ @shAllocatePassthrough(3, worldPos) #if LIGHTING +@shAllocatePassthrough(3, normalPassthrough) +#if VERTEX_LIGHTING @shAllocatePassthrough(3, lightResult) @shAllocatePassthrough(3, directionalResult) +#else +@shAllocatePassthrough(3, colourPassthrough) +#endif #if SHADOWS @shAllocatePassthrough(4, lightSpacePos0) @@ -69,12 +84,13 @@ shNormalInput(float4) shColourInput(float4) - shUniform(float, lightCount) @shAutoConstant(lightCount, light_count) +#if VERTEX_LIGHTING shUniform(float4, lightPosition[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightPosition, light_position_object_space_array, @shGlobalSettingString(num_lights)) shUniform(float4, lightDiffuse[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightDiffuse, light_diffuse_colour_array, @shGlobalSettingString(num_lights)) shUniform(float4, lightAttenuation[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightAttenuation, light_attenuation_array, @shGlobalSettingString(num_lights)) shUniform(float4, lightAmbient) @shAutoConstant(lightAmbient, ambient_light_colour) - +#endif + #if SHADOWS shUniform(float4x4, texViewProjMatrix0) @shAutoConstant(texViewProjMatrix0, texture_viewproj_matrix) #endif @@ -122,6 +138,13 @@ @shPassthroughAssign(worldPos, worldPos.xyz); +#if LIGHTING + @shPassthroughAssign(normalPassthrough, normal.xyz); +#endif +#if LIGHTING && !VERTEX_LIGHTING + @shPassthroughAssign(colourPassthrough, colour.xyz); +#endif + #if LIGHTING #if SHADOWS @@ -139,6 +162,7 @@ #endif +#if VERTEX_LIGHTING // Lighting float3 lightDir; float d; @@ -164,6 +188,7 @@ @shPassthroughAssign(lightResult, lightResult); @shPassthroughAssign(directionalResult, directionalResult); +#endif #endif } @@ -189,6 +214,9 @@ @shForeach(@shPropertyString(num_layers)) shSampler2D(diffuseMap@shIterator) +#if @shPropertyBool(use_normal_map_@shIterator) + shSampler2D(normalMap@shIterator) +#endif @shEndForeach #endif @@ -201,6 +229,15 @@ @shPassthroughFragmentInputs #if LIGHTING + +#if !VERTEX_LIGHTING +shUniform(float4, lightPosition[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightPosition, light_position_array, @shGlobalSettingString(num_lights)) +shUniform(float4, lightDiffuse[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightDiffuse, light_diffuse_colour_array, @shGlobalSettingString(num_lights)) +shUniform(float4, lightAttenuation[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightAttenuation, light_attenuation_array, @shGlobalSettingString(num_lights)) +shUniform(float4, lightAmbient) @shAutoConstant(lightAmbient, ambient_light_colour) +shUniform(float4x4, worldView) @shAutoConstant(worldView, worldview_matrix) +#endif + #if SHADOWS shSampler2D(shadowMap0) shUniform(float2, invShadowmapSize0) @shAutoConstant(invShadowmapSize0, inverse_texture_size, @shPropertyString(shadowtexture_offset)) @@ -220,13 +257,21 @@ #if (UNDERWATER) || (FOG) shUniform(float4x4, worldMatrix) @shAutoConstant(worldMatrix, world_matrix) - shUniform(float4, cameraPos) @shAutoConstant(cameraPos, camera_position) #endif #if UNDERWATER shUniform(float, waterLevel) @shSharedParameter(waterLevel) #endif + +// For specular +#if LIGHTING + shUniform(float3, lightSpec0) @shAutoConstant(lightSpec0, light_specular_colour, 0) + shUniform(float3, lightPos0) @shAutoConstant(lightPos0, light_position, 0) +#endif + +shUniform(float4, cameraPos) @shAutoConstant(cameraPos, camera_position) + SH_START_PROGRAM { @@ -237,12 +282,32 @@ float2 UV = @shPassthroughReceive(UV); float3 worldPos = @shPassthroughReceive(worldPos); - - + +#if LIGHTING + float3 normal = @shPassthroughReceive(normalPassthrough); +#endif + +#if LIGHTING && !VERTEX_LIGHTING + +#if NORMAL_MAP + // derive the tangent space basis + float3 tangent = float3(1,0, 0); + + float3 binormal = normalize(cross(tangent, normal)); + tangent = normalize(cross(normal, binormal)); // note, now we need to re-cross to derive tangent again because it wasn't orthonormal + + // derive final matrix + float3x3 tbn = float3x3(tangent, binormal, normal); + #if SH_GLSL + tbn = transpose(tbn); + #endif +#endif + +#endif + #if UNDERWATER float3 waterEyePos = intercept(worldPos, cameraPos.xyz - worldPos, float3(0,0,1), waterLevel); #endif - #if !IS_FIRST_PASS // Opacity the previous passes should have, i.e. 1 - (opacity of this pass) @@ -252,6 +317,8 @@ float previousAlpha = 1.f; shOutputColour(0) = float4(1,1,1,1); +float3 TSnormal = float3(0,0,1); + #if COMPOSITE_MAP shOutputColour(0).xyz = shSample(compositeMap, UV).xyz; #else @@ -266,39 +333,90 @@ float2 blendUV = (UV - 0.5) * (16.0 / (16.0+1.0)) + 0.5; @shEndForeach - float3 albedo = float3(0,0,0); + float4 albedo = float4(0,0,0,1); float2 layerUV = UV * 16; + float2 thisLayerUV; + float4 normalTex; + + float3 eyeDir = normalize(cameraPos.xyz - worldPos); +#if PARALLAX + float3 TSeyeDir = normalize(shMatrixMult(tbn, eyeDir)); +#endif @shForeach(@shPropertyString(num_layers)) +#if @shPropertyBool(use_normal_map_@shIterator) + normalTex = shSample(normalMap@shIterator, thisLayerUV); +#if @shIterator == 0 && IS_FIRST_PASS + TSnormal = normalize(normalTex.xyz * 2 - 1); +#else + TSnormal = shLerp(TSnormal, normalTex.xyz * 2 - 1, blendValues@shPropertyString(blendmap_component_@shIterator)); +#endif +#endif + thisLayerUV = layerUV; + // required to play nicely with the tangents + thisLayerUV.y *= -1; +#if @shPropertyBool(use_parallax_@shIterator) + thisLayerUV += TSeyeDir.xy * ( normalTex.a * PARALLAX_SCALE + PARALLAX_BIAS ); +#endif #if IS_FIRST_PASS #if @shIterator == 0 // first layer of first pass is the base layer and doesn't need a blend map - albedo = shSample(diffuseMap0, layerUV).rgb; + albedo = shSample(diffuseMap0, layerUV); #else - albedo = shLerp(albedo, shSample(diffuseMap@shIterator, layerUV).rgb, blendValues@shPropertyString(blendmap_component_@shIterator)); + albedo = shLerp(albedo, shSample(diffuseMap@shIterator, thisLayerUV), blendValues@shPropertyString(blendmap_component_@shIterator)); #endif #else #if @shIterator == 0 - albedo = shSample(diffuseMap@shIterator, layerUV).rgb, blendValues@shPropertyString(blendmap_component_@shIterator); + albedo = shSample(diffuseMap@shIterator, layerUV); #else - albedo = shLerp(albedo, shSample(diffuseMap@shIterator, layerUV).rgb, blendValues@shPropertyString(blendmap_component_@shIterator)); + albedo = shLerp(albedo, shSample(diffuseMap@shIterator, thisLayerUV), blendValues@shPropertyString(blendmap_component_@shIterator)); #endif previousAlpha *= 1.f-blendValues@shPropertyString(blendmap_component_@shIterator); #endif + + @shEndForeach - shOutputColour(0).rgb *= albedo; + shOutputColour(0).rgb *= albedo.xyz; #endif #if LIGHTING + +#if VERTEX_LIGHTING // Lighting float3 lightResult = @shPassthroughReceive(lightResult); float3 directionalResult = @shPassthroughReceive(directionalResult); - +#else + +#if NORMAL_MAP + normal = normalize (shMatrixMult( transpose(tbn), TSnormal )); +#endif + + float3 colour = @shPassthroughReceive(colourPassthrough); + float3 lightDir; + float d; + float3 lightResult = float3(0,0,0); + @shForeach(@shGlobalSettingString(num_lights)) + lightDir = lightPosition[@shIterator].xyz - (worldPos * lightPosition[@shIterator].w); + d = length(lightDir); + lightDir = normalize(lightDir); + + lightResult.xyz += lightDiffuse[@shIterator].xyz + * shSaturate(1.0 / ((lightAttenuation[@shIterator].y) + (lightAttenuation[@shIterator].z * d) + (lightAttenuation[@shIterator].w * d * d))) + * max(dot(normal.xyz, lightDir), 0); +#if @shIterator == 0 + float3 directionalResult = lightResult.xyz; +#endif + @shEndForeach + lightResult.xyz += lightAmbient.xyz; + lightResult.xyz *= colour.xyz; + directionalResult.xyz *= colour.xyz; +#endif + // shadows only for the first (directional) light #if SHADOWS float4 lightSpacePos0 = @shPassthroughReceive(lightSpacePos0); @@ -325,6 +443,17 @@ float2 blendUV = (UV - 0.5) * (16.0 / (16.0+1.0)) + 0.5; shOutputColour(0).xyz *= (lightResult - directionalResult * (1.0-shadow)); #endif +#if LIGHTING && !COMPOSITE_MAP + // Specular + float3 light0Dir = normalize(lightPos0.xyz); + + float NdotL = max(dot(normal, light0Dir), 0); + float3 halfVec = normalize (light0Dir + eyeDir); + + float3 specular = pow(max(dot(normal, halfVec), 0), 32) * lightSpec0; + shOutputColour(0).xyz += specular * (1.f-albedo.a) * shadow; +#endif + #if FOG float fogValue = shSaturate((depth - fogParams.y) * fogParams.w); From afa71bb622df10872c2d997731c30b409b4cb374 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 5 Dec 2013 14:23:39 +0100 Subject: [PATCH 398/434] Throw an exception if a BSA is not found --- apps/openmw/engine.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index a729bdda1e..02e7a59550 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -1,6 +1,6 @@ #include "engine.hpp" -#include "components/esm/loadcell.hpp" +#include #include #include @@ -18,6 +18,8 @@ #include #include +#include + #include "mwinput/inputmanagerimp.hpp" #include "mwgui/windowmanagerimp.hpp" @@ -211,7 +213,9 @@ void OMW::Engine::loadBSA() } else { - std::cout << "Archive " << *archive << " not found" << std::endl; + std::stringstream message; + message << "Archive '" << *archive << "' not found"; + throw std::runtime_error(message.str()); } } } From 845bc5f7ebbc82604edef8ee1359d95cb6819220 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 5 Dec 2013 14:28:39 +0100 Subject: [PATCH 399/434] Show fatal exceptions in a message box instead of cerr when running without a terminal --- apps/openmw/main.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 42815e330b..b1bbb14f23 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -1,7 +1,9 @@ #include +#include #include +#include #include #include "engine.hpp" @@ -280,7 +282,11 @@ int main(int argc, char**argv) } catch (std::exception &e) { - std::cout << "\nERROR: " << e.what() << std::endl; + if (isatty(fileno(stdin))) + std::cerr << "\nERROR: " << e.what() << std::endl; + else + SDL_ShowSimpleMessageBox(0, "OpenMW: Fatal error", e.what(), NULL); + return 1; } From 062ea627b37d099e6e5749abd3735a07943fe570 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 5 Dec 2013 15:45:39 +0100 Subject: [PATCH 400/434] Add parallax mapping for objects --- components/nifogre/material.cpp | 4 +++ files/materials/core.h | 2 +- files/materials/objects.mat | 2 ++ files/materials/objects.shader | 53 ++++++++++++++++++++------------- 4 files changed, 40 insertions(+), 21 deletions(-) diff --git a/components/nifogre/material.cpp b/components/nifogre/material.cpp index 55f064c555..8398dbc2ed 100644 --- a/components/nifogre/material.cpp +++ b/components/nifogre/material.cpp @@ -334,6 +334,10 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata, instance->setProperty("detailMapUVSet", sh::makeProperty(new sh::IntValue(texprop->textures[Nif::NiTexturingProperty::DetailTexture].uvSet))); } + bool useParallax = !texName[Nif::NiTexturingProperty::BumpTexture].empty() + && texName[Nif::NiTexturingProperty::BumpTexture].find("_nh.") != std::string::npos; + instance->setProperty("use_parallax", sh::makeProperty(new sh::BooleanValue(useParallax))); + for(int i = 0;i < 7;i++) { if(i == Nif::NiTexturingProperty::BaseTexture || diff --git a/files/materials/core.h b/files/materials/core.h index 6f8179c08d..a912e23562 100644 --- a/files/materials/core.h +++ b/files/materials/core.h @@ -91,7 +91,7 @@ precision mediump float; #define shSamplerCube(name) uniform samplerCube name; @shUseSampler(name) - #define shMatrixMult(m, v) (m * v) + #define shMatrixMult(m, v) ((m) * (v)) #define shOutputPosition gl_Position diff --git a/files/materials/objects.mat b/files/materials/objects.mat index ffba6e7ea8..32787e159b 100644 --- a/files/materials/objects.mat +++ b/files/materials/objects.mat @@ -12,6 +12,7 @@ material openmw_objects_base use_detail_map false emissiveMapUVSet 0 detailMapUVSet 0 + use_parallax false scene_blend default depth_write default @@ -37,6 +38,7 @@ material openmw_objects_base detailMap $detailMap env_map $env_map env_map_color $env_map_color + use_parallax $use_parallax } diffuse $diffuse diff --git a/files/materials/objects.shader b/files/materials/objects.shader index fb2b304262..5a3d872a5b 100644 --- a/files/materials/objects.shader +++ b/files/materials/objects.shader @@ -18,6 +18,10 @@ #define EMISSIVE_MAP @shPropertyHasValue(emissiveMap) #define DETAIL_MAP @shPropertyHasValue(detailMap) +#define PARALLAX @shPropertyBool(use_parallax) +#define PARALLAX_SCALE 0.04 +#define PARALLAX_BIAS -0.02 + // right now we support 2 UV sets max. implementing them is tedious, and we're probably not going to need more #define SECOND_UV_SET (@shPropertyString(emissiveMapUVSet) || @shPropertyString(detailMapUVSet)) @@ -265,7 +269,7 @@ shUniform(float3, env_map_color) @shUniformProperty3f(env_map_color, env_map_color) #endif -#if ENV_MAP || SPECULAR +#if ENV_MAP || SPECULAR || PARALLAX shUniform(float3, cameraPosObjSpace) @shAutoConstant(cameraPosObjSpace, camera_position_object_space) #endif #if SPECULAR @@ -345,21 +349,12 @@ SH_START_PROGRAM { + float4 newUV = UV; + #ifdef NEED_DEPTH float depthPassthrough = objSpacePositionPassthrough.w; #endif - float4 diffuse = shSample(diffuseMap, UV.xy); - shOutputColour(0) = diffuse; - -#if DETAIL_MAP -#if @shPropertyString(detailMapUVSet) - shOutputColour(0) *= shSample(detailMap, UV.zw)*2; -#else - shOutputColour(0) *= shSample(detailMap, UV.xy)*2; -#endif -#endif - #if NEED_NORMAL float3 normal = normalPassthrough; #endif @@ -372,11 +367,33 @@ tbn = transpose(tbn); #endif - float3 TSnormal = shSample(normalMap, UV.xy).xyz * 2 - 1; + float4 normalTex = shSample(normalMap, UV.xy); - normal = normalize (shMatrixMult( transpose(tbn), TSnormal )); + normal = normalize (shMatrixMult( transpose(tbn), normalTex.xyz * 2 - 1 )); #endif +#if ENV_MAP || SPECULAR || PARALLAX + float3 eyeDir = normalize(cameraPosObjSpace.xyz - objSpacePositionPassthrough.xyz); +#endif + +#if PARALLAX + float3 TSeyeDir = normalize(shMatrixMult(tbn, eyeDir)); + + newUV += (TSeyeDir.xyxy * ( normalTex.a * PARALLAX_SCALE + PARALLAX_BIAS )).xyxy; +#endif + + float4 diffuse = shSample(diffuseMap, newUV.xy); + shOutputColour(0) = diffuse; + +#if DETAIL_MAP +#if @shPropertyString(detailMapUVSet) + shOutputColour(0) *= shSample(detailMap, newUV.zw)*2; +#else + shOutputColour(0) *= shSample(detailMap, newUV.xy)*2; +#endif +#endif + + #if !VERTEX_LIGHTING float3 viewPos = shMatrixMult(worldView, float4(objSpacePositionPassthrough.xyz,1)).xyz; float3 viewNormal = normalize(shMatrixMult(worldView, float4(normal.xyz, 0)).xyz); @@ -458,16 +475,12 @@ #if EMISSIVE_MAP #if @shPropertyString(emissiveMapUVSet) - shOutputColour(0).xyz += shSample(emissiveMap, UV.zw).xyz; + shOutputColour(0).xyz += shSample(emissiveMap, newUV.zw).xyz; #else - shOutputColour(0).xyz += shSample(emissiveMap, UV.xy).xyz; + shOutputColour(0).xyz += shSample(emissiveMap, newUV.xy).xyz; #endif #endif -#if ENV_MAP || SPECULAR - float3 eyeDir = normalize(cameraPosObjSpace.xyz - objSpacePositionPassthrough.xyz); -#endif - #if ENV_MAP // Everything looks better with fresnel float facing = 1.0 - max(abs(dot(-eyeDir, normal)), 0); From bfd79bfbe6135430f02e58deb4bc6157110c039e Mon Sep 17 00:00:00 2001 From: Lukasz Gromanowski Date: Fri, 6 Dec 2013 07:36:16 +0100 Subject: [PATCH 401/434] Various fixes for CppCheck warnings. Signed-off-by: Lukasz Gromanowski --- apps/esmtool/record.hpp | 7 ++++++- apps/launcher/datafilespage.cpp | 10 ++-------- apps/opencs/model/filter/andnode.hpp | 2 -- apps/opencs/model/filter/ornode.hpp | 2 -- apps/opencs/model/filter/parser.cpp | 8 ++++---- apps/opencs/model/settings/settingsitem.hpp | 6 +++++- apps/openmw/mwmechanics/aisequence.cpp | 1 + apps/openmw/mwmechanics/character.cpp | 2 +- apps/openmw/mwworld/weather.cpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 2 +- components/esm/loadland.cpp | 4 ++-- components/esm/loadland.hpp | 4 ++-- extern/oics/tinyxml.h | 2 +- libs/openengine/bullet/BtOgre.cpp | 2 ++ 14 files changed, 28 insertions(+), 26 deletions(-) diff --git a/apps/esmtool/record.hpp b/apps/esmtool/record.hpp index 78cf5d436e..45b6d04266 100644 --- a/apps/esmtool/record.hpp +++ b/apps/esmtool/record.hpp @@ -24,7 +24,12 @@ namespace EsmTool bool mPrintPlain; public: - RecordBase () { mPrintPlain = false; } + RecordBase () + : mFlags(0) + , mPrintPlain(false) + { + } + virtual ~RecordBase() {} const std::string &getId() const { diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index d5eb458f80..362d7562c6 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -75,14 +75,8 @@ void Launcher::DataFilesPage::saveSettings(const QString &profile) mLauncherSettings.setValue(QString("Profiles/currentprofile"), ui.profilesComboBox->currentText()); foreach(const ContentSelectorModel::EsmFile *item, items) { - - if (item->gameFiles().size() == 0) { - mLauncherSettings.setMultiValue(QString("Profiles/") + profileName, item->fileName()); - mGameSettings.setMultiValue(QString("content"), item->fileName()); - } else { - mLauncherSettings.setMultiValue(QString("Profiles/") + profileName, item->fileName()); - mGameSettings.setMultiValue(QString("content"), item->fileName()); - } + mLauncherSettings.setMultiValue(QString("Profiles/") + profileName, item->fileName()); + mGameSettings.setMultiValue(QString("content"), item->fileName()); } } diff --git a/apps/opencs/model/filter/andnode.hpp b/apps/opencs/model/filter/andnode.hpp index 980ea554c6..8871757008 100644 --- a/apps/opencs/model/filter/andnode.hpp +++ b/apps/opencs/model/filter/andnode.hpp @@ -7,8 +7,6 @@ namespace CSMFilter { class AndNode : public NAryNode { - bool mAnd; - public: AndNode (const std::vector >& nodes); diff --git a/apps/opencs/model/filter/ornode.hpp b/apps/opencs/model/filter/ornode.hpp index 63ed2f10ce..c39e350956 100644 --- a/apps/opencs/model/filter/ornode.hpp +++ b/apps/opencs/model/filter/ornode.hpp @@ -7,8 +7,6 @@ namespace CSMFilter { class OrNode : public NAryNode { - bool mAnd; - public: OrNode (const std::vector >& nodes); diff --git a/apps/opencs/model/filter/parser.cpp b/apps/opencs/model/filter/parser.cpp index 8f4fcb70c9..6e286d943b 100644 --- a/apps/opencs/model/filter/parser.cpp +++ b/apps/opencs/model/filter/parser.cpp @@ -61,11 +61,11 @@ namespace CSMFilter bool isString() const; }; - Token::Token (Type type) : mType (type) {} + Token::Token (Type type) : mType (type), mNumber(0.0) {} - Token::Token (Type type, const std::string& string) : mType (type), mString (string) {} + Token::Token (Type type, const std::string& string) : mType (type), mString (string), mNumber(0.0) {} - Token::Token (const std::string& string) : mType (Type_String), mString (string) {} + Token::Token (const std::string& string) : mType (Type_String), mString (string), mNumber(0.0) {} Token::Token (double number) : mType (Type_Number), mNumber (number) {} @@ -614,4 +614,4 @@ boost::shared_ptr CSMFilter::Parser::getFilter() const throw std::logic_error ("No filter available"); return mFilter; -} \ No newline at end of file +} diff --git a/apps/opencs/model/settings/settingsitem.hpp b/apps/opencs/model/settings/settingsitem.hpp index a1daee4ac9..87a85e8e4e 100644 --- a/apps/opencs/model/settings/settingsitem.hpp +++ b/apps/opencs/model/settings/settingsitem.hpp @@ -44,7 +44,11 @@ namespace CSMSettings inline QStringPair *getValuePair() { return mValuePair; } /// set value range (spinbox / integer use) - inline void setValuePair (QStringPair valuePair) { mValuePair = new QStringPair(valuePair); } + inline void setValuePair (QStringPair valuePair) + { + delete mValuePair; + mValuePair = new QStringPair(valuePair); + } inline bool isMultivalue () { return mIsMultiValue; } diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index d5fb76eded..6d461e5f63 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -37,6 +37,7 @@ MWMechanics::AiSequence& MWMechanics::AiSequence::operator= (const AiSequence& s { clear(); copy (sequence); + mDone = sequence.mDone; } return *this; diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 7505d34056..68f87ef4c7 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1109,7 +1109,7 @@ void CharacterController::resurrect() if(mAnimation) mAnimation->disable(mCurrentDeath); - mCurrentDeath.empty(); + mCurrentDeath.clear(); mDeathState = CharState_None; } diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 6f5dbe23fe..8b05d2256a 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -473,7 +473,7 @@ void WeatherManager::update(float duration) { // pick a random sound int sound = rand() % 4; - std::string* soundName; + std::string* soundName = NULL; if (sound == 0) soundName = &mThunderSoundID0; else if (sound == 1) soundName = &mThunderSoundID1; else if (sound == 2) soundName = &mThunderSoundID2; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 87a8d7d6f6..a1aad0011e 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -214,7 +214,7 @@ namespace MWWorld : mPlayer (0), mLocalScripts (mStore), mGlobalVariables (0), mSky (true), mCells (mStore, mEsm), mActivationDistanceOverride (mActivationDistanceOverride), - mFallback(fallbackMap), mPlayIntro(0), mTeleportEnabled(true), + mFallback(fallbackMap), mPlayIntro(0), mTeleportEnabled(true), mLevitationEnabled(false), mFacedDistance(FLT_MAX), mGodMode(false) { mPhysics = new PhysicsSystem(renderer); diff --git a/components/esm/loadland.cpp b/components/esm/loadland.cpp index ede200d79d..9c97eaa4de 100644 --- a/components/esm/loadland.cpp +++ b/components/esm/loadland.cpp @@ -160,10 +160,10 @@ void Land::loadData(int flags) } mEsm->restoreContext(mContext); - memset(mLandData->mNormals, 0, LAND_NUM_VERTS * 3); + memset(mLandData->mNormals, 0, sizeof(mLandData->mNormals)); if (mEsm->isNextSub("VNML")) { - condLoad(actual, DATA_VNML, mLandData->mNormals, sizeof(VNML)); + condLoad(actual, DATA_VNML, mLandData->mNormals, sizeof(mLandData->mNormals)); } if (mEsm->isNextSub("VHGT")) { diff --git a/components/esm/loadland.hpp b/components/esm/loadland.hpp index 5649f99801..32abb77993 100644 --- a/components/esm/loadland.hpp +++ b/components/esm/loadland.hpp @@ -72,13 +72,13 @@ struct Land }; #pragma pack(pop) - typedef signed char VNML[LAND_NUM_VERTS * 3]; + typedef signed char VNML; struct LandData { float mHeightOffset; float mHeights[LAND_NUM_VERTS]; - VNML mNormals; + VNML mNormals[LAND_NUM_VERTS * 3]; uint16_t mTextures[LAND_NUM_TEXTURES]; bool mUsingColours; diff --git a/extern/oics/tinyxml.h b/extern/oics/tinyxml.h index e69913b59c..50ad417fe5 100644 --- a/extern/oics/tinyxml.h +++ b/extern/oics/tinyxml.h @@ -349,7 +349,7 @@ protected: { //strncpy( _value, p, *length ); // lots of compilers don't like this function (unsafe), // and the null terminator isn't needed - for( int i=0; p[i] && i<*length; ++i ) { + for( int i=0; i<*length && p[i]; ++i ) { _value[i] = p[i]; } return p + (*length); diff --git a/libs/openengine/bullet/BtOgre.cpp b/libs/openengine/bullet/BtOgre.cpp index b0fa07fd60..de9ea6f57c 100644 --- a/libs/openengine/bullet/BtOgre.cpp +++ b/libs/openengine/bullet/BtOgre.cpp @@ -819,6 +819,8 @@ namespace BtOgre { */ DynamicRenderable::DynamicRenderable() + : mVertexBufferCapacity(0) + , mIndexBufferCapacity(0) { } From baacf91de4bab1487061d3f838adf965f8a399a8 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Fri, 6 Dec 2013 11:37:29 +0100 Subject: [PATCH 402/434] Another windows build fix --- apps/openmw/main.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index b1bbb14f23..0dcc5b36d8 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -282,9 +282,11 @@ int main(int argc, char**argv) } catch (std::exception &e) { +#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX || OGRE_PLATFORM == OGRE_PLATFORM_APPLE if (isatty(fileno(stdin))) std::cerr << "\nERROR: " << e.what() << std::endl; else +#endif SDL_ShowSimpleMessageBox(0, "OpenMW: Fatal error", e.what(), NULL); return 1; From e01085cac533300f0cb1cacb3bca1597a6547d49 Mon Sep 17 00:00:00 2001 From: Lukasz Gromanowski Date: Sat, 7 Dec 2013 02:57:30 +0100 Subject: [PATCH 403/434] Fixes #1015: Player status window scroll state resets on status change Removed resetting scroll state position. Signed-off-by: Lukasz Gromanowski --- apps/openmw/mwgui/statswindow.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index 9facdac404..fabf1bccc6 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -420,8 +420,6 @@ namespace MWGui } mSkillWidgets.clear(); - mSkillView->setViewOffset (MyGUI::IntPoint(0,0)); - const int valueSize = 40; MyGUI::IntCoord coord1(10, 0, mSkillView->getWidth() - (10 + valueSize) - 24, 18); MyGUI::IntCoord coord2(coord1.left + coord1.width, coord1.top, valueSize, coord1.height); From c5e543b91b75f2131efb9816b6cb63e073f21d10 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 6 Dec 2013 17:25:32 +0100 Subject: [PATCH 404/434] Implement NiGeomMorpherController --- components/nifogre/mesh.cpp | 7 +++ components/nifogre/ogrenifloader.cpp | 86 ++++++++++++++++++++++++++-- 2 files changed, 88 insertions(+), 5 deletions(-) diff --git a/components/nifogre/mesh.cpp b/components/nifogre/mesh.cpp index ca92f62d49..ef4fbbe8df 100644 --- a/components/nifogre/mesh.cpp +++ b/components/nifogre/mesh.cpp @@ -116,6 +116,7 @@ void NIFMeshLoader::createSubMesh(Ogre::Mesh *mesh, const Nif::NiTriShape *shape Ogre::HardwareBuffer::Usage vertUsage = Ogre::HardwareBuffer::HBU_STATIC; bool vertShadowBuffer = false; + bool geomMorpherController = false; if(!shape->controller.empty()) { Nif::ControllerPtr ctrl = shape->controller; @@ -124,6 +125,7 @@ void NIFMeshLoader::createSubMesh(Ogre::Mesh *mesh, const Nif::NiTriShape *shape { vertUsage = Ogre::HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY; vertShadowBuffer = true; + geomMorpherController = true; break; } } while(!(ctrl=ctrl->next).empty()); @@ -347,6 +349,11 @@ void NIFMeshLoader::createSubMesh(Ogre::Mesh *mesh, const Nif::NiTriShape *shape if (!mesh->suggestTangentVectorBuildParams(Ogre::VES_TANGENT, src,dest)) mesh->buildTangentVectors(Ogre::VES_TANGENT, src,dest); } + + // Create a dummy vertex animation track if there's a geom morpher controller + // This is required to make Ogre create the buffers we will use for software vertex animation + if (srcVerts.size() && geomMorpherController) + mesh->createAnimation("dummy", 0)->createVertexTrack(1, sub->vertexData, Ogre::VAT_MORPH); } diff --git a/components/nifogre/ogrenifloader.cpp b/components/nifogre/ogrenifloader.cpp index a530d060d2..ec24089b88 100644 --- a/components/nifogre/ogrenifloader.cpp +++ b/components/nifogre/ogrenifloader.cpp @@ -385,12 +385,35 @@ public: private: Ogre::SubEntity *mSubEntity; std::vector mMorphs; + std::vector mValues; + + std::vector mVertices; + + static float interpKey(const Nif::FloatKeyList::VecType &keys, float time) + { + if(time <= keys.front().mTime) + return keys.front().mValue; + + Nif::FloatKeyList::VecType::const_iterator iter(keys.begin()+1); + for(;iter != keys.end();iter++) + { + if(iter->mTime < time) + continue; + + Nif::FloatKeyList::VecType::const_iterator last(iter-1); + float a = (time-last->mTime) / (iter->mTime-last->mTime); + return last->mValue + ((iter->mValue - last->mValue)*a); + } + return keys.back().mValue; + } public: Value(Ogre::SubEntity *subent, const Nif::NiMorphData *data) : mSubEntity(subent) , mMorphs(data->mMorphs) - { } + { + mValues.resize(mMorphs.size()-1, 0.f); + } virtual Ogre::Real getValue() const { @@ -398,9 +421,63 @@ public: return 0.0f; } - virtual void setValue(Ogre::Real value) + virtual void setValue(Ogre::Real time) { - // TODO: Implement + if (mMorphs.size() <= 1) + return; + +#if OGRE_DOUBLE_PRECISION +#error "This code needs to be rewritten for double precision mode" +#endif + + Ogre::VertexData* data = mSubEntity->_getSoftwareVertexAnimVertexData(); + + const Ogre::VertexElement* posElem = + data->vertexDeclaration->findElementBySemantic(Ogre::VES_POSITION); + + Ogre::HardwareVertexBufferSharedPtr vbuf = + data->vertexBufferBinding->getBuffer(posElem->getSource()); + + bool needToUpdate = false; + int i=0; + for (std::vector::iterator it = mMorphs.begin()+1; it != mMorphs.end(); ++it,++i) + { + float val = 0; + if (!it->mData.mKeys.empty()) + val = interpKey(it->mData.mKeys, time); + val = std::max(0.f, std::min(1.f, val)); + + if (val != mValues[i]) + needToUpdate = true; + mValues[i] = val; + } + if (!needToUpdate) + return; + + // The first morph key always contains the original positions + mVertices = mMorphs[0].mVertices; + + i = 0; + for (std::vector::iterator it = mMorphs.begin()+1; it != mMorphs.end(); ++it,++i) + { + float val = mValues[i]; + + if (it->mVertices.size() != mMorphs[0].mVertices.size()) + continue; + + if (val != 0) + { + for (unsigned int v=0; vmVertices[v] * val; + } + } + + if (mVertices.size() * sizeof(float)*3 != vbuf->getSizeInBytes()) + return; + + vbuf->writeData(0, vbuf->getSizeInBytes(), &mVertices[0]); + + mSubEntity->_markBuffersUsedForAnimation(); } }; @@ -480,11 +557,10 @@ class NIFObjectLoader { const Nif::NiGeomMorpherController *geom = static_cast(ctrl.getPtr()); - Ogre::SubEntity *subent = entity->getSubEntity(0); Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ? Ogre::ControllerManager::getSingleton().getFrameTimeSource() : Ogre::ControllerValueRealPtr()); - Ogre::ControllerValueRealPtr dstval(OGRE_NEW GeomMorpherController::Value(subent, geom->data.getPtr())); + Ogre::ControllerValueRealPtr dstval(OGRE_NEW GeomMorpherController::Value(entity->getSubEntity(0), geom->data.getPtr())); GeomMorpherController::Function* function = OGRE_NEW GeomMorpherController::Function(geom, (animflags&Nif::NiNode::AnimFlag_AutoPlay)); objectlist.mMaxControllerLength = std::max(function->mStopTime, objectlist.mMaxControllerLength); From bb70deabb1e06606484fb7ffdec2a8452e07936f Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 7 Dec 2013 14:11:06 +0100 Subject: [PATCH 405/434] Add an incomplete implementation of SayAnimationValue (lip animation) --- apps/openmw/mwrender/npcanimation.cpp | 18 ++++++++++++++++-- apps/openmw/mwrender/npcanimation.hpp | 14 ++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 50e41062a8..4ba49cf550 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -59,6 +59,15 @@ std::string getVampireHead(const std::string& race, bool female) namespace MWRender { +float SayAnimationValue::getValue() const +{ + if (MWBase::Environment::get().getSoundManager()->sayDone(mReference)) + return 0; + else + // TODO: Use the loudness of the currently playing sound + return 1; +} + static NpcAnimation::PartBoneMap createPartListMap() { NpcAnimation::PartBoneMap result; @@ -115,6 +124,8 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, int v { mNpc = mPtr.get()->mBase; + mSayAnimationValue = Ogre::SharedPtr(new SayAnimationValue(mPtr)); + for(size_t i = 0;i < ESM::PRT_Count;i++) { mPartslots[i] = -1; //each slot is empty @@ -558,15 +569,18 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g } // TODO: - // type == ESM::PRT_Head should get an animation source based on the current output of - // the actor's 'say' sound (0 = silent, 1 = loud?). // type == ESM::PRT_Weapon should get an animation source based on the current offset // of the weapon attack animation (from its beginning, or start marker?) std::vector >::iterator ctrl(mObjectParts[type].mControllers.begin()); for(;ctrl != mObjectParts[type].mControllers.end();ctrl++) { if(ctrl->getSource().isNull()) + { ctrl->setSource(mNullAnimationValuePtr); + + if (type == ESM::PRT_Head) + ctrl->setSource(mSayAnimationValue); + } } return true; diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index c33d511ecc..4e252d9d05 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -13,6 +13,18 @@ namespace ESM namespace MWRender { +class SayAnimationValue : public Ogre::ControllerValue +{ +private: + MWWorld::Ptr mReference; +public: + SayAnimationValue(MWWorld::Ptr reference) : mReference(reference) {} + + virtual Ogre::Real getValue() const; + virtual void setValue(Ogre::Real value) + { } +}; + class NpcAnimation : public Animation, public MWWorld::InventoryStoreListener { public: @@ -50,6 +62,8 @@ private: Ogre::Vector3 mFirstPersonOffset; + Ogre::SharedPtr mSayAnimationValue; + void updateNpcBase(); NifOgre::ObjectList insertBoundedPart(const std::string &model, int group, const std::string &bonename, From 742e0e014da428c58dd1361f5702ed533f56fe93 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 7 Dec 2013 14:14:05 +0100 Subject: [PATCH 406/434] Remove more cruft in MessageBox. Fixes inconsistent sizing when close to a newline. --- apps/openmw/mwgui/messagebox.cpp | 31 +++++----------------------- apps/openmw/mwgui/messagebox.hpp | 2 -- files/mygui/openmw_messagebox.layout | 10 ++++----- 3 files changed, 10 insertions(+), 33 deletions(-) diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index 8486218f0b..6718455523 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -141,7 +141,6 @@ namespace MWGui , mMaxTime(0) { // defines - mFixedWidth = 300; mBottomPadding = 20; mNextBoxPadding = 20; @@ -149,41 +148,21 @@ namespace MWGui mMessageWidget->setOverflowToTheLeft(true); mMessageWidget->setCaptionWithReplacing(mMessage); - - MyGUI::IntSize size; - size.width = mFixedWidth; - size.height = 100; // dummy - - mMessageWidget->setSize(size); - - MyGUI::IntSize textSize = mMessageWidget->getTextSize(); - - size.height = mHeight = textSize.height + 20; // this is the padding between the text and the box - - mMainWidget->setSize(size); - size.width -= 15; // this is to center the text (see messagebox.layout, Widget type="Edit" position="-2 -3 0 0") - mMessageWidget->setSize(size); } void MessageBox::update (int height) { MyGUI::IntSize gameWindowSize = MyGUI::RenderManager::getInstance().getViewSize(); - MyGUI::IntCoord coord; - coord.left = (gameWindowSize.width - mFixedWidth)/2; - coord.top = (gameWindowSize.height - mHeight - height - mBottomPadding); + MyGUI::IntPoint pos; + pos.left = (gameWindowSize.width - mMainWidget->getWidth())/2; + pos.top = (gameWindowSize.height - mMainWidget->getHeight() - height - mBottomPadding); - MyGUI::IntSize size; - size.width = mFixedWidth; - size.height = mHeight; - - mMainWidget->setCoord(coord); - mMainWidget->setSize(size); - mMainWidget->setVisible(true); + mMainWidget->setPosition(pos); } int MessageBox::getHeight () { - return mHeight+mNextBoxPadding; // 20 is the padding between this and the next MessageBox + return mMainWidget->getHeight()+mNextBoxPadding; // 20 is the padding between this and the next MessageBox } diff --git a/apps/openmw/mwgui/messagebox.hpp b/apps/openmw/mwgui/messagebox.hpp index ce3a85ab3e..aac4704fa9 100644 --- a/apps/openmw/mwgui/messagebox.hpp +++ b/apps/openmw/mwgui/messagebox.hpp @@ -63,10 +63,8 @@ namespace MWGui protected: MessageBoxManager& mMessageBoxManager; - int mHeight; const std::string& mMessage; MyGUI::EditBox* mMessageWidget; - int mFixedWidth; int mBottomPadding; int mNextBoxPadding; }; diff --git a/files/mygui/openmw_messagebox.layout b/files/mygui/openmw_messagebox.layout index dfdb57648d..b2d29271bc 100644 --- a/files/mygui/openmw_messagebox.layout +++ b/files/mygui/openmw_messagebox.layout @@ -1,11 +1,11 @@ - - - + + + + + From 1624e0fd8a7f9604d6913f270fba6022b83ae669 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 7 Dec 2013 20:12:03 +0100 Subject: [PATCH 407/434] Rename ObjectList to ObjectScene. Wrap it in a SharedPtr so we can automate the destruction routine. --- apps/openmw/mwrender/animation.cpp | 114 ++++++++------------ apps/openmw/mwrender/animation.hpp | 10 +- apps/openmw/mwrender/npcanimation.cpp | 40 ++++--- apps/openmw/mwrender/npcanimation.hpp | 4 +- apps/openmw/mwrender/sky.cpp | 19 ++-- apps/openmw/mwrender/sky.hpp | 4 + components/nifogre/ogrenifloader.cpp | 148 +++++++++++++++----------- components/nifogre/ogrenifloader.hpp | 16 ++- 8 files changed, 178 insertions(+), 177 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index dddbc2c733..42d11aa6d0 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -51,28 +51,6 @@ void Animation::EffectAnimationValue::setValue(Ogre::Real) { } - -void Animation::destroyObjectList(Ogre::SceneManager *sceneMgr, NifOgre::ObjectList &objects) -{ - for(size_t i = 0;i < objects.mLights.size();i++) - { - Ogre::Light *light = objects.mLights[i]; - // If parent is a scene node, it was created specifically for this light. Destroy it now. - if(light->isAttached() && !light->isParentTagPoint()) - sceneMgr->destroySceneNode(light->getParentSceneNode()); - sceneMgr->destroyLight(light); - } - for(size_t i = 0;i < objects.mParticles.size();i++) - sceneMgr->destroyParticleSystem(objects.mParticles[i]); - for(size_t i = 0;i < objects.mEntities.size();i++) - sceneMgr->destroyEntity(objects.mEntities[i]); - objects.mControllers.clear(); - objects.mLights.clear(); - objects.mParticles.clear(); - objects.mEntities.clear(); - objects.mSkelBase = NULL; -} - Animation::Animation(const MWWorld::Ptr &ptr, Ogre::SceneNode *node) : mPtr(ptr) , mCamera(NULL) @@ -90,13 +68,9 @@ Animation::Animation(const MWWorld::Ptr &ptr, Ogre::SceneNode *node) Animation::~Animation() { - for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ++it) - destroyObjectList(mInsert->getCreator(), it->mObjects); + mEffects.clear(); mAnimSources.clear(); - - Ogre::SceneManager *sceneMgr = mInsert->getCreator(); - destroyObjectList(sceneMgr, mObjectRoot); } @@ -105,7 +79,7 @@ void Animation::setObjectRoot(const std::string &model, bool baseonly) OgreAssert(mAnimSources.empty(), "Setting object root while animation sources are set!"); mSkelBase = NULL; - destroyObjectList(mInsert->getCreator(), mObjectRoot); + mObjectRoot.setNull(); if(model.empty()) return; @@ -126,11 +100,11 @@ void Animation::setObjectRoot(const std::string &model, bool baseonly) mObjectRoot = (!baseonly ? NifOgre::Loader::createObjects(mInsert, mdlname) : NifOgre::Loader::createObjectBase(mInsert, mdlname)); - if(mObjectRoot.mSkelBase) + if(mObjectRoot->mSkelBase) { - mSkelBase = mObjectRoot.mSkelBase; + mSkelBase = mObjectRoot->mSkelBase; - Ogre::AnimationStateSet *aset = mObjectRoot.mSkelBase->getAllAnimationStates(); + Ogre::AnimationStateSet *aset = mObjectRoot->mSkelBase->getAllAnimationStates(); Ogre::AnimationStateIterator asiter = aset->getAnimationStateIterator(); while(asiter.hasMoreElements()) { @@ -141,7 +115,7 @@ void Animation::setObjectRoot(const std::string &model, bool baseonly) // Set the bones as manually controlled since we're applying the // transformations manually - Ogre::SkeletonInstance *skelinst = mObjectRoot.mSkelBase->getSkeleton(); + Ogre::SkeletonInstance *skelinst = mObjectRoot->mSkelBase->getSkeleton(); Ogre::Skeleton::BoneIterator boneiter = skelinst->getBoneIterator(); while(boneiter.hasMoreElements()) boneiter.getNext()->setManuallyControlled(true); @@ -162,10 +136,10 @@ void Animation::setObjectRoot(const std::string &model, bool baseonly) else mAttachedObjects.clear(); - for(size_t i = 0;i < mObjectRoot.mControllers.size();i++) + for(size_t i = 0;i < mObjectRoot->mControllers.size();i++) { - if(mObjectRoot.mControllers[i].getSource().isNull()) - mObjectRoot.mControllers[i].setSource(mAnimationValuePtr[0]); + if(mObjectRoot->mControllers[i].getSource().isNull()) + mObjectRoot->mControllers[i].setSource(mAnimationValuePtr[0]); } } @@ -233,15 +207,15 @@ public: } }; -void Animation::setRenderProperties(const NifOgre::ObjectList &objlist, Ogre::uint32 visflags, Ogre::uint8 solidqueue, Ogre::uint8 transqueue, Ogre::Real dist, bool enchantedGlow, Ogre::Vector3* glowColor) +void Animation::setRenderProperties(NifOgre::ObjectScenePtr objlist, Ogre::uint32 visflags, Ogre::uint8 solidqueue, Ogre::uint8 transqueue, Ogre::Real dist, bool enchantedGlow, Ogre::Vector3* glowColor) { - std::for_each(objlist.mEntities.begin(), objlist.mEntities.end(), + std::for_each(objlist->mEntities.begin(), objlist->mEntities.end(), VisQueueSet(visflags, solidqueue, transqueue, dist)); - std::for_each(objlist.mParticles.begin(), objlist.mParticles.end(), + std::for_each(objlist->mParticles.begin(), objlist->mParticles.end(), VisQueueSet(visflags, solidqueue, transqueue, dist)); if (enchantedGlow) - std::for_each(objlist.mEntities.begin(), objlist.mEntities.end(), + std::for_each(objlist->mEntities.begin(), objlist->mEntities.end(), AddGlow(glowColor)); } @@ -340,7 +314,7 @@ void Animation::clearAnimSources() } -void Animation::addExtraLight(Ogre::SceneManager *sceneMgr, NifOgre::ObjectList &objlist, const ESM::Light *light) +void Animation::addExtraLight(Ogre::SceneManager *sceneMgr, NifOgre::ObjectScenePtr objlist, const ESM::Light *light) { const MWWorld::Fallback *fallback = MWBase::Environment::get().getWorld()->getFallback(); @@ -353,8 +327,8 @@ void Animation::addExtraLight(Ogre::SceneManager *sceneMgr, NifOgre::ObjectList if((light->mData.mFlags&ESM::Light::Negative)) color *= -1; - objlist.mLights.push_back(sceneMgr->createLight()); - Ogre::Light *olight = objlist.mLights.back(); + objlist->mLights.push_back(sceneMgr->createLight()); + Ogre::Light *olight = objlist->mLights.back(); olight->setDiffuseColour(color); Ogre::ControllerValueRealPtr src(Ogre::ControllerManager::getSingleton().getFrameTimeSource()); @@ -366,7 +340,7 @@ void Animation::addExtraLight(Ogre::SceneManager *sceneMgr, NifOgre::ObjectList (light->mData.mFlags&ESM::Light::PulseSlow) ? OEngine::Render::LT_PulseSlow : OEngine::Render::LT_Normal )); - objlist.mControllers.push_back(Ogre::Controller(src, dest, func)); + objlist->mControllers.push_back(Ogre::Controller(src, dest, func)); bool interior = !(mPtr.isInCell() && mPtr.getCell()->mCell->isExterior()); bool quadratic = fallback->getFallbackBool("LightAttenuation_OutQuadInLin") ? @@ -392,14 +366,14 @@ void Animation::addExtraLight(Ogre::SceneManager *sceneMgr, NifOgre::ObjectList } // If there's an AttachLight bone, attach the light to that, otherwise put it in the center, - if(objlist.mSkelBase && objlist.mSkelBase->getSkeleton()->hasBone("AttachLight")) - objlist.mSkelBase->attachObjectToBone("AttachLight", olight); + if(objlist->mSkelBase && objlist->mSkelBase->getSkeleton()->hasBone("AttachLight")) + objlist->mSkelBase->attachObjectToBone("AttachLight", olight); else { Ogre::AxisAlignedBox bounds = Ogre::AxisAlignedBox::BOX_NULL; - for(size_t i = 0;i < objlist.mEntities.size();i++) + for(size_t i = 0;i < objlist->mEntities.size();i++) { - Ogre::Entity *ent = objlist.mEntities[i]; + Ogre::Entity *ent = objlist->mEntities[i]; bounds.merge(ent->getBoundingBox()); } @@ -942,8 +916,8 @@ Ogre::Vector3 Animation::runAnimation(float duration) ++stateiter; } - for(size_t i = 0;i < mObjectRoot.mControllers.size();i++) - mObjectRoot.mControllers[i].update(); + for(size_t i = 0;i < mObjectRoot->mControllers.size();i++) + mObjectRoot->mControllers[i].update(); // Apply group controllers for(size_t grp = 0;grp < sNumGroups;grp++) @@ -986,7 +960,7 @@ public: void Animation::enableLights(bool enable) { - std::for_each(mObjectRoot.mLights.begin(), mObjectRoot.mLights.end(), ToggleLight(enable)); + std::for_each(mObjectRoot->mLights.begin(), mObjectRoot->mLights.end(), ToggleLight(enable)); } @@ -1005,7 +979,7 @@ public: Ogre::AxisAlignedBox Animation::getWorldBounds() { Ogre::AxisAlignedBox bounds = Ogre::AxisAlignedBox::BOX_NULL; - std::for_each(mObjectRoot.mEntities.begin(), mObjectRoot.mEntities.end(), MergeBounds(&bounds)); + std::for_each(mObjectRoot->mEntities.begin(), mObjectRoot->mEntities.end(), MergeBounds(&bounds)); return bounds; } @@ -1055,17 +1029,17 @@ void Animation::addEffect(const std::string &model, int effectId, bool loop, con params.mEffectId = effectId; params.mBoneName = bonename; - for(size_t i = 0;i < params.mObjects.mControllers.size();i++) + for(size_t i = 0;i < params.mObjects->mControllers.size();i++) { - if(params.mObjects.mControllers[i].getSource().isNull()) - params.mObjects.mControllers[i].setSource(Ogre::SharedPtr (new EffectAnimationValue())); + if(params.mObjects->mControllers[i].getSource().isNull()) + params.mObjects->mControllers[i].setSource(Ogre::SharedPtr (new EffectAnimationValue())); } if (!texture.empty()) { - for(size_t i = 0;i < params.mObjects.mParticles.size(); ++i) + for(size_t i = 0;i < params.mObjects->mParticles.size(); ++i) { - Ogre::ParticleSystem* partSys = params.mObjects.mParticles[i]; + Ogre::ParticleSystem* partSys = params.mObjects->mParticles[i]; sh::Factory::getInstance()._ensureMaterial(partSys->getMaterialName(), "Default"); Ogre::MaterialPtr mat = Ogre::MaterialManager::getSingleton().getByName(partSys->getMaterialName()); static int count = 0; @@ -1099,7 +1073,6 @@ void Animation::removeEffect(int effectId) { if (it->mEffectId == effectId) { - destroyObjectList(mInsert->getCreator(), it->mObjects); mEffects.erase(it); return; } @@ -1119,32 +1092,31 @@ void Animation::updateEffects(float duration) { for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ) { - NifOgre::ObjectList& objects = it->mObjects; - for(size_t i = 0; i < objects.mControllers.size() ;i++) + NifOgre::ObjectScenePtr objects = it->mObjects; + for(size_t i = 0; i < objects->mControllers.size() ;i++) { - EffectAnimationValue* value = dynamic_cast(objects.mControllers[i].getSource().get()); + EffectAnimationValue* value = dynamic_cast(objects->mControllers[i].getSource().get()); if (value) value->addTime(duration); - objects.mControllers[i].update(); + objects->mControllers[i].update(); } - if (objects.mControllers[0].getSource()->getValue() >= objects.mMaxControllerLength) + if (objects->mControllers[0].getSource()->getValue() >= objects->mMaxControllerLength) { if (it->mLoop) { // Start from the beginning again; carry over the remainder - float remainder = objects.mControllers[0].getSource()->getValue() - objects.mMaxControllerLength; - for(size_t i = 0; i < objects.mControllers.size() ;i++) + float remainder = objects->mControllers[0].getSource()->getValue() - objects->mMaxControllerLength; + for(size_t i = 0; i < objects->mControllers.size() ;i++) { - EffectAnimationValue* value = dynamic_cast(objects.mControllers[i].getSource().get()); + EffectAnimationValue* value = dynamic_cast(objects->mControllers[i].getSource().get()); if (value) value->resetTime(remainder); } } else { - destroyObjectList(mInsert->getCreator(), it->mObjects); it = mEffects.erase(it); continue; } @@ -1214,16 +1186,16 @@ public: bool ObjectAnimation::canBatch() const { - if(!mObjectRoot.mParticles.empty() || !mObjectRoot.mLights.empty() || !mObjectRoot.mControllers.empty()) + if(!mObjectRoot->mParticles.empty() || !mObjectRoot->mLights.empty() || !mObjectRoot->mControllers.empty()) return false; - return std::find_if(mObjectRoot.mEntities.begin(), mObjectRoot.mEntities.end(), - FindEntityTransparency()) == mObjectRoot.mEntities.end(); + return std::find_if(mObjectRoot->mEntities.begin(), mObjectRoot->mEntities.end(), + FindEntityTransparency()) == mObjectRoot->mEntities.end(); } void ObjectAnimation::fillBatch(Ogre::StaticGeometry *sg) { - std::vector::reverse_iterator iter = mObjectRoot.mEntities.rbegin(); - for(;iter != mObjectRoot.mEntities.rend();++iter) + std::vector::reverse_iterator iter = mObjectRoot->mEntities.rbegin(); + for(;iter != mObjectRoot->mEntities.rend();++iter) { Ogre::Node *node = (*iter)->getParentNode(); sg->addEntity(*iter, node->_getDerivedPosition(), node->_getDerivedOrientation(), node->_getDerivedScale()); diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index e28aecbc13..b11b2b0a59 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -112,7 +112,7 @@ protected: struct EffectParams { std::string mModelName; // Just here so we don't add the same effect twice - NifOgre::ObjectList mObjects; + NifOgre::ObjectScenePtr mObjects; int mEffectId; bool mLoop; std::string mBoneName; @@ -125,7 +125,7 @@ protected: Ogre::SceneNode *mInsert; Ogre::Entity *mSkelBase; - NifOgre::ObjectList mObjectRoot; + NifOgre::ObjectScenePtr mObjectRoot; AnimSourceList mAnimSources; Ogre::Node *mAccumRoot; Ogre::Node *mNonAccumRoot; @@ -187,11 +187,9 @@ protected: void addAnimSource(const std::string &model); /** Adds an additional light to the given object list using the specified ESM record. */ - void addExtraLight(Ogre::SceneManager *sceneMgr, NifOgre::ObjectList &objlist, const ESM::Light *light); + void addExtraLight(Ogre::SceneManager *sceneMgr, NifOgre::ObjectScenePtr objlist, const ESM::Light *light); - static void destroyObjectList(Ogre::SceneManager *sceneMgr, NifOgre::ObjectList &objects); - - static void setRenderProperties(const NifOgre::ObjectList &objlist, Ogre::uint32 visflags, Ogre::uint8 solidqueue, + static void setRenderProperties(NifOgre::ObjectScenePtr objlist, Ogre::uint32 visflags, Ogre::uint8 solidqueue, Ogre::uint8 transqueue, Ogre::Real dist=0.0f, bool enchantedGlow=false, Ogre::Vector3* glowColor=NULL); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 4ba49cf550..a57816f344 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -106,10 +106,6 @@ NpcAnimation::~NpcAnimation() { if (!mListenerDisabled) mPtr.getClass().getInventoryStore(mPtr).setListener(NULL, mPtr); - - Ogre::SceneManager *sceneMgr = mInsert->getCreator(); - for(size_t i = 0;i < ESM::PRT_Count;i++) - destroyObjectList(sceneMgr, mObjectParts[i]); } @@ -447,18 +443,18 @@ public: } }; -NifOgre::ObjectList NpcAnimation::insertBoundedPart(const std::string &model, int group, const std::string &bonename, bool enchantedGlow, Ogre::Vector3* glowColor) +NifOgre::ObjectScenePtr NpcAnimation::insertBoundedPart(const std::string &model, int group, const std::string &bonename, bool enchantedGlow, Ogre::Vector3* glowColor) { - NifOgre::ObjectList objects = NifOgre::Loader::createObjects(mSkelBase, bonename, mInsert, model); + NifOgre::ObjectScenePtr objects = NifOgre::Loader::createObjects(mSkelBase, bonename, mInsert, model); setRenderProperties(objects, (mViewMode == VM_FirstPerson) ? RV_FirstPerson : mVisibilityFlags, RQG_Main, RQG_Alpha, 0, enchantedGlow, glowColor); - std::for_each(objects.mEntities.begin(), objects.mEntities.end(), SetObjectGroup(group)); - std::for_each(objects.mParticles.begin(), objects.mParticles.end(), SetObjectGroup(group)); + std::for_each(objects->mEntities.begin(), objects->mEntities.end(), SetObjectGroup(group)); + std::for_each(objects->mParticles.begin(), objects->mParticles.end(), SetObjectGroup(group)); - if(objects.mSkelBase) + if(objects->mSkelBase) { - Ogre::AnimationStateSet *aset = objects.mSkelBase->getAllAnimationStates(); + Ogre::AnimationStateSet *aset = objects->mSkelBase->getAllAnimationStates(); Ogre::AnimationStateIterator asiter = aset->getAnimationStateIterator(); while(asiter.hasMoreElements()) { @@ -466,7 +462,7 @@ NifOgre::ObjectList NpcAnimation::insertBoundedPart(const std::string &model, in state->setEnabled(false); state->setLoop(false); } - Ogre::SkeletonInstance *skelinst = objects.mSkelBase->getSkeleton(); + Ogre::SkeletonInstance *skelinst = objects->mSkelBase->getSkeleton(); Ogre::Skeleton::BoneIterator boneiter = skelinst->getBoneIterator(); while(boneiter.hasMoreElements()) boneiter.getNext()->setManuallyControlled(true); @@ -494,11 +490,13 @@ Ogre::Vector3 NpcAnimation::runAnimation(float timepassed) for(size_t i = 0;i < ESM::PRT_Count;i++) { - std::vector >::iterator ctrl(mObjectParts[i].mControllers.begin()); - for(;ctrl != mObjectParts[i].mControllers.end();ctrl++) + if (mObjectParts[i].isNull()) + continue; + std::vector >::iterator ctrl(mObjectParts[i]->mControllers.begin()); + for(;ctrl != mObjectParts[i]->mControllers.end();ctrl++) ctrl->update(); - Ogre::Entity *ent = mObjectParts[i].mSkelBase; + Ogre::Entity *ent = mObjectParts[i]->mSkelBase; if(!ent) continue; updateSkeletonInstance(baseinst, ent->getSkeleton()); ent->getAllAnimationStates()->_notifyDirty(); @@ -512,7 +510,7 @@ void NpcAnimation::removeIndividualPart(ESM::PartReferenceType type) mPartPriorities[type] = 0; mPartslots[type] = -1; - destroyObjectList(mInsert->getCreator(), mObjectParts[type]); + mObjectParts[type].setNull(); } void NpcAnimation::reserveIndividualPart(ESM::PartReferenceType type, int group, int priority) @@ -544,12 +542,12 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g mPartPriorities[type] = priority; mObjectParts[type] = insertBoundedPart(mesh, group, sPartList.at(type), enchantedGlow, glowColor); - if(mObjectParts[type].mSkelBase) + if(mObjectParts[type]->mSkelBase) { - Ogre::SkeletonInstance *skel = mObjectParts[type].mSkelBase->getSkeleton(); - if(mObjectParts[type].mSkelBase->isParentTagPoint()) + Ogre::SkeletonInstance *skel = mObjectParts[type]->mSkelBase->getSkeleton(); + if(mObjectParts[type]->mSkelBase->isParentTagPoint()) { - Ogre::Node *root = mObjectParts[type].mSkelBase->getParentNode(); + Ogre::Node *root = mObjectParts[type]->mSkelBase->getParentNode(); if(skel->hasBone("BoneOffset")) { Ogre::Bone *offset = skel->getBone("BoneOffset"); @@ -571,8 +569,8 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g // TODO: // type == ESM::PRT_Weapon should get an animation source based on the current offset // of the weapon attack animation (from its beginning, or start marker?) - std::vector >::iterator ctrl(mObjectParts[type].mControllers.begin()); - for(;ctrl != mObjectParts[type].mControllers.end();ctrl++) + std::vector >::iterator ctrl(mObjectParts[type]->mControllers.begin()); + for(;ctrl != mObjectParts[type]->mControllers.end();ctrl++) { if(ctrl->getSource().isNull()) { diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 4e252d9d05..9626632688 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -46,7 +46,7 @@ private: bool mListenerDisabled; // Bounded Parts - NifOgre::ObjectList mObjectParts[ESM::PRT_Count]; + NifOgre::ObjectScenePtr mObjectParts[ESM::PRT_Count]; const ESM::NPC *mNpc; std::string mHeadModel; @@ -66,7 +66,7 @@ private: void updateNpcBase(); - NifOgre::ObjectList insertBoundedPart(const std::string &model, int group, const std::string &bonename, + NifOgre::ObjectScenePtr insertBoundedPart(const std::string &model, int group, const std::string &bonename, bool enchantedGlow, Ogre::Vector3* glowColor=NULL); void removeIndividualPart(ESM::PartReferenceType type); diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 03e14dc07d..39f7ccc854 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -285,10 +285,10 @@ void SkyManager::create() // Stars mAtmosphereNight = mRootNode->createChildSceneNode(); - NifOgre::ObjectList objects = NifOgre::Loader::createObjects(mAtmosphereNight, "meshes\\sky_night_01.nif"); - for(size_t i = 0, matidx = 0;i < objects.mEntities.size();i++) + NifOgre::ObjectScenePtr objects = NifOgre::Loader::createObjects(mAtmosphereNight, "meshes\\sky_night_01.nif"); + for(size_t i = 0, matidx = 0;i < objects->mEntities.size();i++) { - Entity* night1_ent = objects.mEntities[i]; + Entity* night1_ent = objects->mEntities[i]; night1_ent->setRenderQueueGroup(RQG_SkiesEarly+1); night1_ent->setVisibilityFlags(RV_Sky); night1_ent->setCastShadows(false); @@ -307,14 +307,14 @@ void SkyManager::create() night1_ent->getSubEntity(j)->setMaterialName(matName); } } - + mObjects.push_back(objects); // Atmosphere (day) mAtmosphereDay = mRootNode->createChildSceneNode(); objects = NifOgre::Loader::createObjects(mAtmosphereDay, "meshes\\sky_atmosphere.nif"); - for(size_t i = 0;i < objects.mEntities.size();i++) + for(size_t i = 0;i < objects->mEntities.size();i++) { - Entity* atmosphere_ent = objects.mEntities[i]; + Entity* atmosphere_ent = objects->mEntities[i]; atmosphere_ent->setCastShadows(false); atmosphere_ent->setRenderQueueGroup(RQG_SkiesEarly); atmosphere_ent->setVisibilityFlags(RV_Sky); @@ -325,14 +325,14 @@ void SkyManager::create() // Using infinite AAB here to prevent being clipped by the custom near clip plane used for reflections/refractions atmosphere_ent->getMesh()->_setBounds (aabInf); } - + mObjects.push_back(objects); // Clouds SceneNode* clouds_node = mRootNode->createChildSceneNode(); objects = NifOgre::Loader::createObjects(clouds_node, "meshes\\sky_clouds_01.nif"); - for(size_t i = 0;i < objects.mEntities.size();i++) + for(size_t i = 0;i < objects->mEntities.size();i++) { - Entity* clouds_ent = objects.mEntities[i]; + Entity* clouds_ent = objects->mEntities[i]; clouds_ent->setVisibilityFlags(RV_Sky); clouds_ent->setRenderQueueGroup(RQG_SkiesEarly+5); for(unsigned int j = 0;j < clouds_ent->getNumSubEntities();j++) @@ -341,6 +341,7 @@ void SkyManager::create() // Using infinite AAB here to prevent being clipped by the custom near clip plane used for reflections/refractions clouds_ent->getMesh()->_setBounds (aabInf); } + mObjects.push_back(objects); mCreated = true; } diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index 3df8846cd4..965907a979 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -11,6 +11,8 @@ #include +#include + #include "../mwworld/weather.hpp" @@ -196,6 +198,8 @@ namespace MWRender Ogre::SceneNode* mAtmosphereDay; Ogre::SceneNode* mAtmosphereNight; + std::vector mObjects; + // remember some settings so we don't have to apply them again if they didnt change Ogre::String mClouds; Ogre::String mNextClouds; diff --git a/components/nifogre/ogrenifloader.cpp b/components/nifogre/ogrenifloader.cpp index ec24089b88..44c3042c68 100644 --- a/components/nifogre/ogrenifloader.cpp +++ b/components/nifogre/ogrenifloader.cpp @@ -47,6 +47,28 @@ namespace NifOgre { + +ObjectScene::~ObjectScene() +{ + for(size_t i = 0;i < mLights.size();i++) + { + Ogre::Light *light = mLights[i]; + // If parent is a scene node, it was created specifically for this light. Destroy it now. + if(light->isAttached() && !light->isParentTagPoint()) + mSceneMgr->destroySceneNode(light->getParentSceneNode()); + mSceneMgr->destroyLight(light); + } + for(size_t i = 0;i < mParticles.size();i++) + mSceneMgr->destroyParticleSystem(mParticles[i]); + for(size_t i = 0;i < mEntities.size();i++) + mSceneMgr->destroyEntity(mEntities[i]); + mControllers.clear(); + mLights.clear(); + mParticles.clear(); + mEntities.clear(); + mSkelBase = NULL; +} + // FIXME: Should not be here. class DefaultFunction : public Ogre::ControllerFunction { @@ -504,7 +526,7 @@ class NIFObjectLoader static void createEntity(const std::string &name, const std::string &group, - Ogre::SceneManager *sceneMgr, ObjectList &objectlist, + Ogre::SceneManager *sceneMgr, ObjectScenePtr scene, const Nif::Node *node, int flags, int animflags) { const Nif::NiTriShape *shape = static_cast(node); @@ -521,16 +543,16 @@ class NIFObjectLoader Ogre::Entity *entity = sceneMgr->createEntity(fullname); entity->setVisible(!(flags&Nif::NiNode::Flag_Hidden)); - objectlist.mEntities.push_back(entity); - if(objectlist.mSkelBase) + scene->mEntities.push_back(entity); + if(scene->mSkelBase) { if(entity->hasSkeleton()) - entity->shareSkeletonInstanceWith(objectlist.mSkelBase); + entity->shareSkeletonInstanceWith(scene->mSkelBase); else { int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, shape->recIndex); - Ogre::Bone *trgtbone = objectlist.mSkelBase->getSkeleton()->getBone(trgtid); - objectlist.mSkelBase->attachObjectToBone(trgtbone->getName(), entity); + Ogre::Bone *trgtbone = scene->mSkelBase->getSkeleton()->getBone(trgtid); + scene->mSkelBase->attachObjectToBone(trgtbone->getName(), entity); } } @@ -548,10 +570,10 @@ class NIFObjectLoader Ogre::ControllerValueRealPtr dstval(OGRE_NEW UVController::Value(material, uv->data.getPtr())); UVController::Function* function = OGRE_NEW UVController::Function(uv, (animflags&Nif::NiNode::AnimFlag_AutoPlay)); - objectlist.mMaxControllerLength = std::max(function->mStopTime, objectlist.mMaxControllerLength); + scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); Ogre::ControllerFunctionRealPtr func(function); - objectlist.mControllers.push_back(Ogre::Controller(srcval, dstval, func)); + scene->mControllers.push_back(Ogre::Controller(srcval, dstval, func)); } else if(ctrl->recType == Nif::RC_NiGeomMorpherController) { @@ -563,10 +585,10 @@ class NIFObjectLoader Ogre::ControllerValueRealPtr dstval(OGRE_NEW GeomMorpherController::Value(entity->getSubEntity(0), geom->data.getPtr())); GeomMorpherController::Function* function = OGRE_NEW GeomMorpherController::Function(geom, (animflags&Nif::NiNode::AnimFlag_AutoPlay)); - objectlist.mMaxControllerLength = std::max(function->mStopTime, objectlist.mMaxControllerLength); + scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); Ogre::ControllerFunctionRealPtr func(function); - objectlist.mControllers.push_back(Ogre::Controller(srcval, dstval, func)); + scene->mControllers.push_back(Ogre::Controller(srcval, dstval, func)); } ctrl = ctrl->next; } @@ -647,7 +669,7 @@ class NIFObjectLoader } static void createParticleSystem(const std::string &name, const std::string &group, - Ogre::SceneNode *sceneNode, ObjectList &objectlist, + Ogre::SceneNode *sceneNode, ObjectScenePtr scene, const Nif::Node *partnode, int flags, int partflags) { const Nif::NiAutoNormalParticlesData *particledata = NULL; @@ -696,8 +718,8 @@ class NIFObjectLoader if(!partctrl->emitter.empty()) { int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, partctrl->emitter->recIndex); - Ogre::Bone *trgtbone = objectlist.mSkelBase->getSkeleton()->getBone(trgtid); - createParticleEmitterAffectors(partsys, partctrl, trgtbone, objectlist.mSkelBase->getName()); + Ogre::Bone *trgtbone = scene->mSkelBase->getSkeleton()->getBone(trgtid); + createParticleEmitterAffectors(partsys, partctrl, trgtbone, scene->mSkelBase->getName()); } Ogre::ControllerValueRealPtr srcval((partflags&Nif::NiNode::ParticleFlag_AutoPlay) ? @@ -707,20 +729,20 @@ class NIFObjectLoader ParticleSystemController::Function* function = OGRE_NEW ParticleSystemController::Function(partctrl, (partflags&Nif::NiNode::ParticleFlag_AutoPlay)); - objectlist.mMaxControllerLength = std::max(function->mStopTime, objectlist.mMaxControllerLength); + scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); Ogre::ControllerFunctionRealPtr func(function); - objectlist.mControllers.push_back(Ogre::Controller(srcval, dstval, func)); + scene->mControllers.push_back(Ogre::Controller(srcval, dstval, func)); } ctrl = ctrl->next; } partsys->setVisible(!(flags&Nif::NiNode::Flag_Hidden)); - objectlist.mParticles.push_back(partsys); + scene->mParticles.push_back(partsys); } - static void createNodeControllers(const std::string &name, Nif::ControllerPtr ctrl, ObjectList &objectlist, int animflags) + static void createNodeControllers(const std::string &name, Nif::ControllerPtr ctrl, ObjectScenePtr scene, int animflags) { do { if(ctrl->recType == Nif::RC_NiVisController) @@ -728,17 +750,17 @@ class NIFObjectLoader const Nif::NiVisController *vis = static_cast(ctrl.getPtr()); int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, ctrl->target->recIndex); - Ogre::Bone *trgtbone = objectlist.mSkelBase->getSkeleton()->getBone(trgtid); + Ogre::Bone *trgtbone = scene->mSkelBase->getSkeleton()->getBone(trgtid); Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ? Ogre::ControllerManager::getSingleton().getFrameTimeSource() : Ogre::ControllerValueRealPtr()); Ogre::ControllerValueRealPtr dstval(OGRE_NEW VisController::Value(trgtbone, vis->data.getPtr())); VisController::Function* function = OGRE_NEW VisController::Function(vis, (animflags&Nif::NiNode::AnimFlag_AutoPlay)); - objectlist.mMaxControllerLength = std::max(function->mStopTime, objectlist.mMaxControllerLength); + scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); Ogre::ControllerFunctionRealPtr func(function); - objectlist.mControllers.push_back(Ogre::Controller(srcval, dstval, func)); + scene->mControllers.push_back(Ogre::Controller(srcval, dstval, func)); } else if(ctrl->recType == Nif::RC_NiKeyframeController) { @@ -746,16 +768,16 @@ class NIFObjectLoader if(!key->data.empty()) { int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, ctrl->target->recIndex); - Ogre::Bone *trgtbone = objectlist.mSkelBase->getSkeleton()->getBone(trgtid); + Ogre::Bone *trgtbone = scene->mSkelBase->getSkeleton()->getBone(trgtid); Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ? Ogre::ControllerManager::getSingleton().getFrameTimeSource() : Ogre::ControllerValueRealPtr()); Ogre::ControllerValueRealPtr dstval(OGRE_NEW KeyframeController::Value(trgtbone, key->data.getPtr())); KeyframeController::Function* function = OGRE_NEW KeyframeController::Function(key, (animflags&Nif::NiNode::AnimFlag_AutoPlay)); - objectlist.mMaxControllerLength = std::max(function->mStopTime, objectlist.mMaxControllerLength); + scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); Ogre::ControllerFunctionRealPtr func(function); - objectlist.mControllers.push_back(Ogre::Controller(srcval, dstval, func)); + scene->mControllers.push_back(Ogre::Controller(srcval, dstval, func)); } } ctrl = ctrl->next; @@ -804,7 +826,7 @@ class NIFObjectLoader static void createObjects(const std::string &name, const std::string &group, Ogre::SceneNode *sceneNode, const Nif::Node *node, - ObjectList &objectlist, int flags, int animflags, int partflags) + ObjectScenePtr scene, int flags, int animflags, int partflags) { // Do not create objects for the collision shape (includes all children) if(node->recType == Nif::RC_RootCollisionNode) @@ -830,7 +852,7 @@ class NIFObjectLoader const Nif::NiTextKeyExtraData *tk = static_cast(e.getPtr()); int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, node->recIndex); - extractTextKeys(tk, objectlist.mTextKeys[trgtid]); + extractTextKeys(tk, scene->mTextKeys[trgtid]); } else if(e->recType == Nif::RC_NiStringExtraData) { @@ -849,7 +871,7 @@ class NIFObjectLoader } if(!node->controller.empty() && (node->parent || node->recType != Nif::RC_NiNode)) - createNodeControllers(name, node->controller, objectlist, animflags); + createNodeControllers(name, node->controller, scene, animflags); if(node->recType == Nif::RC_NiCamera) { @@ -858,13 +880,13 @@ class NIFObjectLoader if(node->recType == Nif::RC_NiTriShape && !(flags&0x80000000)) { - createEntity(name, group, sceneNode->getCreator(), objectlist, node, flags, animflags); + createEntity(name, group, sceneNode->getCreator(), scene, node, flags, animflags); } if((node->recType == Nif::RC_NiAutoNormalParticles || node->recType == Nif::RC_NiRotatingParticles) && !(flags&0x40000000)) { - createParticleSystem(name, group, sceneNode, objectlist, node, flags, partflags); + createParticleSystem(name, group, sceneNode, scene, node, flags, partflags); } const Nif::NiNode *ninode = dynamic_cast(node); @@ -874,14 +896,14 @@ class NIFObjectLoader for(size_t i = 0;i < children.length();i++) { if(!children[i].empty()) - createObjects(name, group, sceneNode, children[i].getPtr(), objectlist, flags, animflags, partflags); + createObjects(name, group, sceneNode, children[i].getPtr(), scene, flags, animflags, partflags); } } } static void createSkelBase(const std::string &name, const std::string &group, Ogre::SceneManager *sceneMgr, const Nif::Node *node, - ObjectList &objectlist) + ObjectScenePtr scene) { /* This creates an empty mesh to which a skeleton gets attached. This * is to ensure we have an entity with a skeleton instance, even if all @@ -890,12 +912,12 @@ class NIFObjectLoader if(meshMgr.getByName(name).isNull()) NIFMeshLoader::createMesh(name, name, group, ~(size_t)0); - objectlist.mSkelBase = sceneMgr->createEntity(name); - objectlist.mEntities.push_back(objectlist.mSkelBase); + scene->mSkelBase = sceneMgr->createEntity(name); + scene->mEntities.push_back(scene->mSkelBase); } public: - static void load(Ogre::SceneNode *sceneNode, ObjectList &objectlist, const std::string &name, const std::string &group, int flags=0) + static void load(Ogre::SceneNode *sceneNode, ObjectScenePtr scene, const std::string &name, const std::string &group, int flags=0) { Nif::NIFFile::ptr nif = Nif::NIFFile::create(name); if(nif->numRoots() < 1) @@ -919,9 +941,9 @@ public: !NIFSkeletonLoader::createSkeleton(name, group, node).isNull()) { // Create a base skeleton entity if this NIF needs one - createSkelBase(name, group, sceneNode->getCreator(), node, objectlist); + createSkelBase(name, group, sceneNode->getCreator(), node, scene); } - createObjects(name, group, sceneNode, node, objectlist, flags, 0, 0); + createObjects(name, group, sceneNode, node, scene, flags, 0, 0); } static void loadKf(Ogre::Skeleton *skel, const std::string &name, @@ -984,37 +1006,37 @@ public: }; -ObjectList Loader::createObjects(Ogre::SceneNode *parentNode, std::string name, const std::string &group) +ObjectScenePtr Loader::createObjects(Ogre::SceneNode *parentNode, std::string name, const std::string &group) { - ObjectList objectlist; + ObjectScenePtr scene = ObjectScenePtr (new ObjectScene(parentNode->getCreator()));; Misc::StringUtils::toLower(name); - NIFObjectLoader::load(parentNode, objectlist, name, group); + NIFObjectLoader::load(parentNode, scene, name, group); - for(size_t i = 0;i < objectlist.mEntities.size();i++) + for(size_t i = 0;i < scene->mEntities.size();i++) { - Ogre::Entity *entity = objectlist.mEntities[i]; + Ogre::Entity *entity = scene->mEntities[i]; if(!entity->isAttached()) parentNode->attachObject(entity); } - return objectlist; + return scene; } -ObjectList Loader::createObjects(Ogre::Entity *parent, const std::string &bonename, +ObjectScenePtr Loader::createObjects(Ogre::Entity *parent, const std::string &bonename, Ogre::SceneNode *parentNode, std::string name, const std::string &group) { - ObjectList objectlist; + ObjectScenePtr scene = ObjectScenePtr (new ObjectScene(parentNode->getCreator())); Misc::StringUtils::toLower(name); - NIFObjectLoader::load(parentNode, objectlist, name, group); + NIFObjectLoader::load(parentNode, scene, name, group); bool isskinned = false; - for(size_t i = 0;i < objectlist.mEntities.size();i++) + for(size_t i = 0;i < scene->mEntities.size();i++) { - Ogre::Entity *ent = objectlist.mEntities[i]; - if(objectlist.mSkelBase != ent && ent->hasSkeleton()) + Ogre::Entity *ent = scene->mEntities[i]; + if(scene->mSkelBase != ent && ent->hasSkeleton()) { isskinned = true; break; @@ -1029,12 +1051,12 @@ ObjectList Loader::createObjects(Ogre::Entity *parent, const std::string &bonena { std::string filter = "@shape=tri "+bonename; Misc::StringUtils::toLower(filter); - for(size_t i = 0;i < objectlist.mEntities.size();i++) + for(size_t i = 0;i < scene->mEntities.size();i++) { - Ogre::Entity *entity = objectlist.mEntities[i]; + Ogre::Entity *entity = scene->mEntities[i]; if(entity->hasSkeleton()) { - if(entity == objectlist.mSkelBase || + if(entity == scene->mSkelBase || entity->getMesh()->getName().find(filter) != std::string::npos) parentNode->attachObject(entity); } @@ -1047,9 +1069,9 @@ ObjectList Loader::createObjects(Ogre::Entity *parent, const std::string &bonena } else { - for(size_t i = 0;i < objectlist.mEntities.size();i++) + for(size_t i = 0;i < scene->mEntities.size();i++) { - Ogre::Entity *entity = objectlist.mEntities[i]; + Ogre::Entity *entity = scene->mEntities[i]; if(!entity->isAttached()) { Ogre::TagPoint *tag = parent->attachObjectToBone(bonename, entity); @@ -1058,32 +1080,32 @@ ObjectList Loader::createObjects(Ogre::Entity *parent, const std::string &bonena } } - for(size_t i = 0;i < objectlist.mParticles.size();i++) + for(size_t i = 0;i < scene->mParticles.size();i++) { - Ogre::ParticleSystem *partsys = objectlist.mParticles[i]; + Ogre::ParticleSystem *partsys = scene->mParticles[i]; if(partsys->isAttached()) partsys->detachFromParent(); - Ogre::TagPoint *tag = objectlist.mSkelBase->attachObjectToBone( - objectlist.mSkelBase->getSkeleton()->getRootBone()->getName(), partsys); + Ogre::TagPoint *tag = scene->mSkelBase->attachObjectToBone( + scene->mSkelBase->getSkeleton()->getRootBone()->getName(), partsys); tag->setScale(scale); } - return objectlist; + return scene; } -ObjectList Loader::createObjectBase(Ogre::SceneNode *parentNode, std::string name, const std::string &group) +ObjectScenePtr Loader::createObjectBase(Ogre::SceneNode *parentNode, std::string name, const std::string &group) { - ObjectList objectlist; + ObjectScenePtr scene = ObjectScenePtr (new ObjectScene(parentNode->getCreator())); Misc::StringUtils::toLower(name); - NIFObjectLoader::load(parentNode, objectlist, name, group, 0xC0000000); + NIFObjectLoader::load(parentNode, scene, name, group, 0xC0000000); - if(objectlist.mSkelBase) - parentNode->attachObject(objectlist.mSkelBase); + if(scene->mSkelBase) + parentNode->attachObject(scene->mSkelBase); - return objectlist; + return scene; } diff --git a/components/nifogre/ogrenifloader.hpp b/components/nifogre/ogrenifloader.hpp index de06dd3d50..94d4aa6745 100644 --- a/components/nifogre/ogrenifloader.hpp +++ b/components/nifogre/ogrenifloader.hpp @@ -39,12 +39,14 @@ namespace NifOgre typedef std::multimap TextKeyMap; static const char sTextKeyExtraDataID[] = "TextKeyExtraData"; -struct ObjectList { +struct ObjectScene { Ogre::Entity *mSkelBase; std::vector mEntities; std::vector mParticles; std::vector mLights; + Ogre::SceneManager* mSceneMgr; + // The maximum length on any of the controllers. For animations with controllers, but no text keys, consider this the animation length. float mMaxControllerLength; @@ -52,24 +54,28 @@ struct ObjectList { std::vector > mControllers; - ObjectList() : mSkelBase(0), mMaxControllerLength(0) + ObjectScene(Ogre::SceneManager* sceneMgr) : mSkelBase(0), mMaxControllerLength(0), mSceneMgr(sceneMgr) { } + + ~ObjectScene(); }; +typedef Ogre::SharedPtr ObjectScenePtr; + class Loader { public: - static ObjectList createObjects(Ogre::Entity *parent, const std::string &bonename, + static ObjectScenePtr createObjects(Ogre::Entity *parent, const std::string &bonename, Ogre::SceneNode *parentNode, std::string name, const std::string &group="General"); - static ObjectList createObjects(Ogre::SceneNode *parentNode, + static ObjectScenePtr createObjects(Ogre::SceneNode *parentNode, std::string name, const std::string &group="General"); - static ObjectList createObjectBase(Ogre::SceneNode *parentNode, + static ObjectScenePtr createObjectBase(Ogre::SceneNode *parentNode, std::string name, const std::string &group="General"); From 8d63f8eea221e2aff758673e15cb373d64b4381b Mon Sep 17 00:00:00 2001 From: Lukasz Gromanowski Date: Sat, 7 Dec 2013 21:00:46 +0100 Subject: [PATCH 408/434] Fixes #998: Setting the max health should also set the current health Added setting current value of dynamic stat in OpSetDynamic class. Signed-off-by: Lukasz Gromanowski --- apps/openmw/mwscript/statsextensions.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 603515ff4a..6cd483ae12 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -209,6 +209,7 @@ namespace MWScript .getDynamic (mIndex)); stat.setModified (value, 0); + stat.setCurrent(value); MWWorld::Class::get (ptr).getCreatureStats (ptr).setDynamic (mIndex, stat); } From 51a9f0111cc9740ddd126c8b84d83ac7d9cac1fb Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 8 Dec 2013 16:38:13 +0100 Subject: [PATCH 409/434] Implement NiAlphaController and NiMaterialColorController --- components/CMakeLists.txt | 2 +- components/nifogre/controller.hpp | 95 ++++++++ components/nifogre/material.cpp | 3 +- components/nifogre/ogrenifloader.cpp | 313 +++++++++++++++++---------- components/nifogre/ogrenifloader.hpp | 15 ++ 5 files changed, 306 insertions(+), 122 deletions(-) create mode 100644 components/nifogre/controller.hpp diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 59fb084a8e..50ac236c8a 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -19,7 +19,7 @@ add_component_dir (nif ) add_component_dir (nifogre - ogrenifloader skeleton material mesh particles + ogrenifloader skeleton material mesh particles controller ) add_component_dir (nifbullet diff --git a/components/nifogre/controller.hpp b/components/nifogre/controller.hpp new file mode 100644 index 0000000000..6d7f6ab3fb --- /dev/null +++ b/components/nifogre/controller.hpp @@ -0,0 +1,95 @@ +#ifndef COMPONENTS_NIFOGRE_CONTROLLER_H +#define COMPONENTS_NIFOGRE_CONTROLLER_H + +#include +#include + +namespace NifOgre +{ + + class ValueInterpolator + { + protected: + float interpKey(const Nif::FloatKeyList::VecType &keys, float time, float def=0.f) const + { + if (keys.size() == 0) + return def; + + if(time <= keys.front().mTime) + return keys.front().mValue; + + Nif::FloatKeyList::VecType::const_iterator iter(keys.begin()+1); + for(;iter != keys.end();iter++) + { + if(iter->mTime < time) + continue; + + Nif::FloatKeyList::VecType::const_iterator last(iter-1); + float a = (time-last->mTime) / (iter->mTime-last->mTime); + return last->mValue + ((iter->mValue - last->mValue)*a); + } + return keys.back().mValue; + } + + Ogre::Vector3 interpKey(const Nif::Vector3KeyList::VecType &keys, float time) const + { + if(time <= keys.front().mTime) + return keys.front().mValue; + + Nif::Vector3KeyList::VecType::const_iterator iter(keys.begin()+1); + for(;iter != keys.end();iter++) + { + if(iter->mTime < time) + continue; + + Nif::Vector3KeyList::VecType::const_iterator last(iter-1); + float a = (time-last->mTime) / (iter->mTime-last->mTime); + return last->mValue + ((iter->mValue - last->mValue)*a); + } + return keys.back().mValue; + } + }; + + // FIXME: Should not be here. + class DefaultFunction : public Ogre::ControllerFunction + { + private: + float mFrequency; + float mPhase; + float mStartTime; + public: + float mStopTime; + + public: + DefaultFunction(const Nif::Controller *ctrl, bool deltaInput) + : Ogre::ControllerFunction(deltaInput) + , mFrequency(ctrl->frequency) + , mPhase(ctrl->phase) + , mStartTime(ctrl->timeStart) + , mStopTime(ctrl->timeStop) + { + if(mDeltaInput) + mDeltaCount = mPhase; + } + + virtual Ogre::Real calculate(Ogre::Real value) + { + if(mDeltaInput) + { + mDeltaCount += value*mFrequency; + if(mDeltaCount < mStartTime) + mDeltaCount = mStopTime - std::fmod(mStartTime - mDeltaCount, + mStopTime - mStartTime); + mDeltaCount = std::fmod(mDeltaCount - mStartTime, + mStopTime - mStartTime) + mStartTime; + return mDeltaCount; + } + + value = std::min(mStopTime, std::max(mStartTime, value+mPhase)); + return value; + } + }; + +} + +#endif diff --git a/components/nifogre/material.cpp b/components/nifogre/material.cpp index 8398dbc2ed..bef0ec1d10 100644 --- a/components/nifogre/material.cpp +++ b/components/nifogre/material.cpp @@ -237,7 +237,8 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata, Nif::ControllerPtr ctrls = matprop->controller; while(!ctrls.empty()) { - warn("Unhandled material controller "+ctrls->recName+" in "+name); + if (ctrls->recType != Nif::RC_NiAlphaController && ctrls->recType != Nif::RC_NiMaterialColorController) + warn("Unhandled material controller "+ctrls->recName+" in "+name); ctrls = ctrls->next; } } diff --git a/components/nifogre/ogrenifloader.cpp b/components/nifogre/ogrenifloader.cpp index 44c3042c68..b71d4c4ee9 100644 --- a/components/nifogre/ogrenifloader.cpp +++ b/components/nifogre/ogrenifloader.cpp @@ -37,16 +37,56 @@ #include #include +#include + #include #include #include "skeleton.hpp" #include "material.hpp" #include "mesh.hpp" +#include "controller.hpp" namespace NifOgre { +Ogre::MaterialPtr MaterialControllerManager::getWritableMaterial(Ogre::MovableObject *movable) +{ + if (mClonedMaterials.find(movable) != mClonedMaterials.end()) + return mClonedMaterials[movable]; + + else + { + Ogre::MaterialPtr mat; + if (Ogre::Entity* ent = dynamic_cast(movable)) + mat = ent->getSubEntity(0)->getMaterial(); + else if (Ogre::ParticleSystem* partSys = dynamic_cast(movable)) + mat = Ogre::MaterialManager::getSingleton().getByName(partSys->getMaterialName()); + + // Make sure techniques are created + sh::Factory::getInstance()._ensureMaterial(mat->getName(), "Default"); + + static int count=0; + mat = mat->clone(mat->getName() + Ogre::StringConverter::toString(count++)); + + mClonedMaterials[movable] = mat; + + if (Ogre::Entity* ent = dynamic_cast(movable)) + ent->getSubEntity(0)->setMaterial(mat); + else if (Ogre::ParticleSystem* partSys = dynamic_cast(movable)) + partSys->setMaterialName(mat->getName()); + + return mat; + } +} + +MaterialControllerManager::~MaterialControllerManager() +{ + for (std::map::iterator it = mClonedMaterials.begin(); it != mClonedMaterials.end(); ++it) + { + Ogre::MaterialManager::getSingleton().remove(it->second->getName()); + } +} ObjectScene::~ObjectScene() { @@ -69,44 +109,100 @@ ObjectScene::~ObjectScene() mSkelBase = NULL; } -// FIXME: Should not be here. -class DefaultFunction : public Ogre::ControllerFunction +class AlphaController { -private: - float mFrequency; - float mPhase; - float mStartTime; public: - float mStopTime; - -public: - DefaultFunction(const Nif::Controller *ctrl, bool deltaInput) - : Ogre::ControllerFunction(deltaInput) - , mFrequency(ctrl->frequency) - , mPhase(ctrl->phase) - , mStartTime(ctrl->timeStart) - , mStopTime(ctrl->timeStop) + class Value : public Ogre::ControllerValue, public ValueInterpolator { - if(mDeltaInput) - mDeltaCount = mPhase; - } + private: + Ogre::MovableObject* mMovable; + Nif::FloatKeyList mData; + MaterialControllerManager* mMaterialControllerMgr; - virtual Ogre::Real calculate(Ogre::Real value) - { - if(mDeltaInput) + public: + Value(Ogre::MovableObject *movable, const Nif::NiFloatData *data, MaterialControllerManager* materialControllerMgr) + : mMovable(movable) + , mData(data->mKeyList) + , mMaterialControllerMgr(materialControllerMgr) { - mDeltaCount += value*mFrequency; - if(mDeltaCount < mStartTime) - mDeltaCount = mStopTime - std::fmod(mStartTime - mDeltaCount, - mStopTime - mStartTime); - mDeltaCount = std::fmod(mDeltaCount - mStartTime, - mStopTime - mStartTime) + mStartTime; - return mDeltaCount; } - value = std::min(mStopTime, std::max(mStartTime, value+mPhase)); - return value; - } + virtual Ogre::Real getValue() const + { + // Should not be called + return 0.0f; + } + + virtual void setValue(Ogre::Real time) + { + float value = interpKey(mData.mKeys, time); + Ogre::MaterialPtr mat = mMaterialControllerMgr->getWritableMaterial(mMovable); + Ogre::Material::TechniqueIterator techs = mat->getTechniqueIterator(); + while(techs.hasMoreElements()) + { + Ogre::Technique *tech = techs.getNext(); + Ogre::Technique::PassIterator passes = tech->getPassIterator(); + while(passes.hasMoreElements()) + { + Ogre::Pass *pass = passes.getNext(); + Ogre::ColourValue diffuse = pass->getDiffuse(); + diffuse.a = value; + pass->setDiffuse(diffuse); + } + } + } + }; + + typedef DefaultFunction Function; +}; + +class MaterialColorController +{ +public: + class Value : public Ogre::ControllerValue, public ValueInterpolator + { + private: + Ogre::MovableObject* mMovable; + Nif::Vector3KeyList mData; + MaterialControllerManager* mMaterialControllerMgr; + + public: + Value(Ogre::MovableObject *movable, const Nif::NiPosData *data, MaterialControllerManager* materialControllerMgr) + : mMovable(movable) + , mData(data->mKeyList) + , mMaterialControllerMgr(materialControllerMgr) + { + } + + virtual Ogre::Real getValue() const + { + // Should not be called + return 0.0f; + } + + virtual void setValue(Ogre::Real time) + { + Ogre::Vector3 value = interpKey(mData.mKeys, time); + Ogre::MaterialPtr mat = mMaterialControllerMgr->getWritableMaterial(mMovable); + Ogre::Material::TechniqueIterator techs = mat->getTechniqueIterator(); + while(techs.hasMoreElements()) + { + Ogre::Technique *tech = techs.getNext(); + Ogre::Technique::PassIterator passes = tech->getPassIterator(); + while(passes.hasMoreElements()) + { + Ogre::Pass *pass = passes.getNext(); + Ogre::ColourValue diffuse = pass->getDiffuse(); + diffuse.r = value.x; + diffuse.g = value.y; + diffuse.b = value.z; + pass->setDiffuse(diffuse); + } + } + } + }; + + typedef DefaultFunction Function; }; class VisController @@ -185,48 +281,14 @@ public: class KeyframeController { public: - class Value : public NodeTargetValue + class Value : public NodeTargetValue, public ValueInterpolator { private: Nif::QuaternionKeyList mRotations; Nif::Vector3KeyList mTranslations; Nif::FloatKeyList mScales; - static float interpKey(const Nif::FloatKeyList::VecType &keys, float time) - { - if(time <= keys.front().mTime) - return keys.front().mValue; - - Nif::FloatKeyList::VecType::const_iterator iter(keys.begin()+1); - for(;iter != keys.end();iter++) - { - if(iter->mTime < time) - continue; - - Nif::FloatKeyList::VecType::const_iterator last(iter-1); - float a = (time-last->mTime) / (iter->mTime-last->mTime); - return last->mValue + ((iter->mValue - last->mValue)*a); - } - return keys.back().mValue; - } - - static Ogre::Vector3 interpKey(const Nif::Vector3KeyList::VecType &keys, float time) - { - if(time <= keys.front().mTime) - return keys.front().mValue; - - Nif::Vector3KeyList::VecType::const_iterator iter(keys.begin()+1); - for(;iter != keys.end();iter++) - { - if(iter->mTime < time) - continue; - - Nif::Vector3KeyList::VecType::const_iterator last(iter-1); - float a = (time-last->mTime) / (iter->mTime-last->mTime); - return last->mValue + ((iter->mValue - last->mValue)*a); - } - return keys.back().mValue; - } + using ValueInterpolator::interpKey; static Ogre::Quaternion interpKey(const Nif::QuaternionKeyList::VecType &keys, float time) { @@ -298,43 +360,24 @@ public: class UVController { public: - class Value : public Ogre::ControllerValue + class Value : public Ogre::ControllerValue, public ValueInterpolator { private: - Ogre::MaterialPtr mMaterial; + Ogre::MovableObject* mMovable; Nif::FloatKeyList mUTrans; Nif::FloatKeyList mVTrans; Nif::FloatKeyList mUScale; Nif::FloatKeyList mVScale; - - static float lookupValue(const Nif::FloatKeyList &keys, float time, float def) - { - if(keys.mKeys.size() == 0) - return def; - - if(time <= keys.mKeys.front().mTime) - return keys.mKeys.front().mValue; - - Nif::FloatKeyList::VecType::const_iterator iter(keys.mKeys.begin()+1); - for(;iter != keys.mKeys.end();iter++) - { - if(iter->mTime < time) - continue; - - Nif::FloatKeyList::VecType::const_iterator last(iter-1); - float a = (time-last->mTime) / (iter->mTime-last->mTime); - return last->mValue + ((iter->mValue - last->mValue)*a); - } - return keys.mKeys.back().mValue; - } + MaterialControllerManager* mMaterialControllerMgr; public: - Value(const Ogre::MaterialPtr &material, const Nif::NiUVData *data) - : mMaterial(material) + Value(Ogre::MovableObject* movable, const Nif::NiUVData *data, MaterialControllerManager* materialControllerMgr) + : mMovable(movable) , mUTrans(data->mKeyList[0]) , mVTrans(data->mKeyList[1]) , mUScale(data->mKeyList[2]) , mVScale(data->mKeyList[3]) + , mMaterialControllerMgr(materialControllerMgr) { } virtual Ogre::Real getValue() const @@ -345,12 +388,14 @@ public: virtual void setValue(Ogre::Real value) { - float uTrans = lookupValue(mUTrans, value, 0.0f); - float vTrans = lookupValue(mVTrans, value, 0.0f); - float uScale = lookupValue(mUScale, value, 1.0f); - float vScale = lookupValue(mVScale, value, 1.0f); + float uTrans = interpKey(mUTrans.mKeys, value, 0.0f); + float vTrans = interpKey(mVTrans.mKeys, value, 0.0f); + float uScale = interpKey(mUScale.mKeys, value, 1.0f); + float vScale = interpKey(mVScale.mKeys, value, 1.0f); - Ogre::Material::TechniqueIterator techs = mMaterial->getTechniqueIterator(); + Ogre::MaterialPtr material = mMaterialControllerMgr->getWritableMaterial(mMovable); + + Ogre::Material::TechniqueIterator techs = material->getTechniqueIterator(); while(techs.hasMoreElements()) { Ogre::Technique *tech = techs.getNext(); @@ -402,7 +447,7 @@ public: class GeomMorpherController { public: - class Value : public Ogre::ControllerValue + class Value : public Ogre::ControllerValue, public ValueInterpolator { private: Ogre::SubEntity *mSubEntity; @@ -411,24 +456,6 @@ public: std::vector mVertices; - static float interpKey(const Nif::FloatKeyList::VecType &keys, float time) - { - if(time <= keys.front().mTime) - return keys.front().mValue; - - Nif::FloatKeyList::VecType::const_iterator iter(keys.begin()+1); - for(;iter != keys.end();iter++) - { - if(iter->mTime < time) - continue; - - Nif::FloatKeyList::VecType::const_iterator last(iter-1); - float a = (time-last->mTime) / (iter->mTime-last->mTime); - return last->mValue + ((iter->mValue - last->mValue)*a); - } - return keys.back().mValue; - } - public: Value(Ogre::SubEntity *subent, const Nif::NiMorphData *data) : mSubEntity(subent) @@ -563,11 +590,10 @@ class NIFObjectLoader { const Nif::NiUVController *uv = static_cast(ctrl.getPtr()); - const Ogre::MaterialPtr &material = entity->getSubEntity(0)->getMaterial(); Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ? Ogre::ControllerManager::getSingleton().getFrameTimeSource() : Ogre::ControllerValueRealPtr()); - Ogre::ControllerValueRealPtr dstval(OGRE_NEW UVController::Value(material, uv->data.getPtr())); + Ogre::ControllerValueRealPtr dstval(OGRE_NEW UVController::Value(entity, uv->data.getPtr(), &scene->mMaterialControllerMgr)); UVController::Function* function = OGRE_NEW UVController::Function(uv, (animflags&Nif::NiNode::AnimFlag_AutoPlay)); scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); @@ -592,8 +618,53 @@ class NIFObjectLoader } ctrl = ctrl->next; } + + createMaterialControllers(shape, entity, animflags, scene); } + static void createMaterialControllers (const Nif::Node* node, Ogre::MovableObject* movable, int animflags, ObjectScenePtr scene) + { + const Nif::NiTexturingProperty *texprop = NULL; + const Nif::NiMaterialProperty *matprop = NULL; + const Nif::NiAlphaProperty *alphaprop = NULL; + const Nif::NiVertexColorProperty *vertprop = NULL; + const Nif::NiZBufferProperty *zprop = NULL; + const Nif::NiSpecularProperty *specprop = NULL; + const Nif::NiWireframeProperty *wireprop = NULL; + node->getProperties(texprop, matprop, alphaprop, vertprop, zprop, specprop, wireprop); + + if(matprop) + { + Nif::ControllerPtr ctrls = matprop->controller; + while(!ctrls.empty()) + { + Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ? + Ogre::ControllerManager::getSingleton().getFrameTimeSource() : + Ogre::ControllerValueRealPtr()); + + if (ctrls->recType == Nif::RC_NiAlphaController) + { + const Nif::NiAlphaController *alphaCtrl = dynamic_cast(ctrls.getPtr()); + Ogre::ControllerValueRealPtr dstval(OGRE_NEW AlphaController::Value(movable, alphaCtrl->data.getPtr(), &scene->mMaterialControllerMgr)); + AlphaController::Function* function = OGRE_NEW AlphaController::Function(alphaCtrl, (animflags&Nif::NiNode::AnimFlag_AutoPlay)); + scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); + Ogre::ControllerFunctionRealPtr func(function); + scene->mControllers.push_back(Ogre::Controller(srcval, dstval, func)); + } + else if (ctrls->recType == Nif::RC_NiMaterialColorController) + { + const Nif::NiMaterialColorController *matCtrl = dynamic_cast(ctrls.getPtr()); + Ogre::ControllerValueRealPtr dstval(OGRE_NEW MaterialColorController::Value(movable, matCtrl->data.getPtr(), &scene->mMaterialControllerMgr)); + AlphaController::Function* function = OGRE_NEW AlphaController::Function(matCtrl, (animflags&Nif::NiNode::AnimFlag_AutoPlay)); + scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); + Ogre::ControllerFunctionRealPtr func(function); + scene->mControllers.push_back(Ogre::Controller(srcval, dstval, func)); + } + + ctrls = ctrls->next; + } + } + } static void createParticleEmitterAffectors(Ogre::ParticleSystem *partsys, const Nif::NiParticleSystemController *partctrl, Ogre::Bone* bone, @@ -670,7 +741,7 @@ class NIFObjectLoader static void createParticleSystem(const std::string &name, const std::string &group, Ogre::SceneNode *sceneNode, ObjectScenePtr scene, - const Nif::Node *partnode, int flags, int partflags) + const Nif::Node *partnode, int flags, int partflags, int animflags) { const Nif::NiAutoNormalParticlesData *particledata = NULL; if(partnode->recType == Nif::RC_NiAutoNormalParticles) @@ -739,6 +810,8 @@ class NIFObjectLoader partsys->setVisible(!(flags&Nif::NiNode::Flag_Hidden)); scene->mParticles.push_back(partsys); + + createMaterialControllers(partnode, partsys, animflags, scene); } @@ -886,7 +959,7 @@ class NIFObjectLoader if((node->recType == Nif::RC_NiAutoNormalParticles || node->recType == Nif::RC_NiRotatingParticles) && !(flags&0x40000000)) { - createParticleSystem(name, group, sceneNode, scene, node, flags, partflags); + createParticleSystem(name, group, sceneNode, scene, node, flags, partflags, animflags); } const Nif::NiNode *ninode = dynamic_cast(node); diff --git a/components/nifogre/ogrenifloader.hpp b/components/nifogre/ogrenifloader.hpp index 94d4aa6745..0fbc5b10eb 100644 --- a/components/nifogre/ogrenifloader.hpp +++ b/components/nifogre/ogrenifloader.hpp @@ -37,6 +37,19 @@ namespace NifOgre { +/** + * @brief Clones materials as necessary to not make controllers affect other objects (that share the original material). + */ +class MaterialControllerManager +{ +public: + ~MaterialControllerManager(); + Ogre::MaterialPtr getWritableMaterial (Ogre::MovableObject* movable); + +private: + std::map mClonedMaterials; +}; + typedef std::multimap TextKeyMap; static const char sTextKeyExtraDataID[] = "TextKeyExtraData"; struct ObjectScene { @@ -52,6 +65,8 @@ struct ObjectScene { std::map mTextKeys; + MaterialControllerManager mMaterialControllerMgr; + std::vector > mControllers; ObjectScene(Ogre::SceneManager* sceneMgr) : mSkelBase(0), mMaxControllerLength(0), mSceneMgr(sceneMgr) From 9fcb4fad5c53890f79a9b158993f8821f6a193d1 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 8 Dec 2013 18:51:56 +0100 Subject: [PATCH 410/434] Implement NiFlipController --- components/nif/controller.hpp | 2 +- components/nifogre/material.hpp | 2 +- components/nifogre/ogrenifloader.cpp | 97 ++++++++++++++++++++++++++-- 3 files changed, 95 insertions(+), 6 deletions(-) diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index 011e0e4452..e44f4a6f30 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -306,7 +306,7 @@ public: class NiFlipController : public Controller { public: - int mTexSlot; + int mTexSlot; // NiTexturingProperty::TextureType float mDelta; // Time between two flips. delta = (start_time - stop_time) / num_sources NiSourceTextureList mSources; diff --git a/components/nifogre/material.hpp b/components/nifogre/material.hpp index 8843ac6c6c..b02c7c2366 100644 --- a/components/nifogre/material.hpp +++ b/components/nifogre/material.hpp @@ -37,9 +37,9 @@ class NIFMaterialLoader { static std::map sMaterialMap; +public: static std::string findTextureName(const std::string &filename); -public: static Ogre::String getMaterial(const Nif::ShapeData *shapedata, const Ogre::String &name, const Ogre::String &group, const Nif::NiTexturingProperty *texprop, diff --git a/components/nifogre/ogrenifloader.cpp b/components/nifogre/ogrenifloader.cpp index b71d4c4ee9..4819be4ca5 100644 --- a/components/nifogre/ogrenifloader.cpp +++ b/components/nifogre/ogrenifloader.cpp @@ -109,6 +109,74 @@ ObjectScene::~ObjectScene() mSkelBase = NULL; } +// Animates a texture +class FlipController +{ +public: + class Value : public Ogre::ControllerValue + { + private: + Ogre::MovableObject* mMovable; + int mTexSlot; + float mDelta; + std::vector mTextures; + MaterialControllerManager* mMaterialControllerMgr; + + public: + Value(Ogre::MovableObject *movable, const Nif::NiFlipController *ctrl, MaterialControllerManager* materialControllerMgr) + : mMovable(movable) + , mMaterialControllerMgr(materialControllerMgr) + { + mTexSlot = ctrl->mTexSlot; + mDelta = ctrl->mDelta; + for (unsigned int i=0; imSources.length(); ++i) + { + const Nif::NiSourceTexture* tex = ctrl->mSources[i].getPtr(); + if (!tex->external) + std::cerr << "Warning: Found internal texture, ignoring." << std::endl; + mTextures.push_back(NIFMaterialLoader::findTextureName(tex->filename)); + } + } + + virtual Ogre::Real getValue() const + { + // Should not be called + return 0.0f; + } + + virtual void setValue(Ogre::Real time) + { + if (mDelta == 0) + return; + int curTexture = int(time / mDelta) % mTextures.size(); + + Ogre::MaterialPtr mat = mMaterialControllerMgr->getWritableMaterial(mMovable); + Ogre::Material::TechniqueIterator techs = mat->getTechniqueIterator(); + while(techs.hasMoreElements()) + { + Ogre::Technique *tech = techs.getNext(); + Ogre::Technique::PassIterator passes = tech->getPassIterator(); + while(passes.hasMoreElements()) + { + Ogre::Pass *pass = passes.getNext(); + Ogre::Pass::TextureUnitStateIterator textures = pass->getTextureUnitStateIterator(); + while (textures.hasMoreElements()) + { + Ogre::TextureUnitState *texture = textures.getNext(); + if ((texture->getName() == "diffuseMap" && mTexSlot == Nif::NiTexturingProperty::BaseTexture) + || (texture->getName() == "normalMap" && mTexSlot == Nif::NiTexturingProperty::BumpTexture) + || (texture->getName() == "detailMap" && mTexSlot == Nif::NiTexturingProperty::DetailTexture) + || (texture->getName() == "emissiveMap" && mTexSlot == Nif::NiTexturingProperty::GlowTexture)) + texture->setTextureName(mTextures[curTexture]); + } + } + } + } + }; + + typedef DefaultFunction Function; +}; + class AlphaController { public: @@ -633,15 +701,15 @@ class NIFObjectLoader const Nif::NiWireframeProperty *wireprop = NULL; node->getProperties(texprop, matprop, alphaprop, vertprop, zprop, specprop, wireprop); + Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ? + Ogre::ControllerManager::getSingleton().getFrameTimeSource() : + Ogre::ControllerValueRealPtr()); + if(matprop) { Nif::ControllerPtr ctrls = matprop->controller; while(!ctrls.empty()) { - Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ? - Ogre::ControllerManager::getSingleton().getFrameTimeSource() : - Ogre::ControllerValueRealPtr()); - if (ctrls->recType == Nif::RC_NiAlphaController) { const Nif::NiAlphaController *alphaCtrl = dynamic_cast(ctrls.getPtr()); @@ -661,6 +729,27 @@ class NIFObjectLoader scene->mControllers.push_back(Ogre::Controller(srcval, dstval, func)); } + ctrls = ctrls->next; + } + } + if (texprop) + { + Nif::ControllerPtr ctrls = texprop->controller; + while(!ctrls.empty()) + { + if (ctrls->recType == Nif::RC_NiFlipController) + { + const Nif::NiFlipController *flipCtrl = dynamic_cast(ctrls.getPtr()); + + + Ogre::ControllerValueRealPtr dstval(OGRE_NEW FlipController::Value( + movable, flipCtrl, &scene->mMaterialControllerMgr)); + FlipController::Function* function = OGRE_NEW FlipController::Function(flipCtrl, (animflags&Nif::NiNode::AnimFlag_AutoPlay)); + scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); + Ogre::ControllerFunctionRealPtr func(function); + scene->mControllers.push_back(Ogre::Controller(srcval, dstval, func)); + } + ctrls = ctrls->next; } } From 594cc693b27f5c4a6621ce7d8f63a9c5b27edcab Mon Sep 17 00:00:00 2001 From: Lukasz Gromanowski Date: Sun, 8 Dec 2013 21:47:43 +0100 Subject: [PATCH 411/434] Fixes #1006: Many NPCs have 0 skill Added calculation of skill values for NPC with mNpdtType set to NPC_WITH_AUTOCALCULATED_STATS (their NPDT is 12). Signed-off-by: Lukasz Gromanowski --- apps/esmtool/record.cpp | 5 +- apps/openmw/mwclass/npc.cpp | 93 ++++++++++++++++++++++++++-- apps/openmw/mwmechanics/npcstats.cpp | 4 +- components/esm/loadnpc.cpp | 12 ++-- components/esm/loadnpc.hpp | 9 ++- 5 files changed, 106 insertions(+), 17 deletions(-) diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index e9fb1c5379..cc09452c91 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -989,8 +989,7 @@ void Record::print() std::cout << " Faction: " << mData.mFaction << std::endl; std::cout << " Flags: " << npcFlags(mData.mFlags) << std::endl; - // Seriously? - if (mData.mNpdt52.mGold == -10) + if (mData.mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) { std::cout << " Level: " << mData.mNpdt12.mLevel << std::endl; std::cout << " Reputation: " << (int)mData.mNpdt12.mReputation << std::endl; @@ -1022,7 +1021,7 @@ void Record::print() std::cout << " Luck: " << (int)mData.mNpdt52.mLuck << std::endl; std::cout << " Skills:" << std::endl; - for (int i = 0; i != 27; i++) + for (int i = 0; i != ESM::Skill::Length; i++) std::cout << " " << skillLabel(i) << ": " << (int)((unsigned char)mData.mNpdt52.mSkills[i]) << std::endl; diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 8ff8081bc7..e7c10d3c81 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -63,7 +63,6 @@ namespace bool male = (npc->mFlags & ESM::NPC::Female) == 0; int level = creatureStats.getLevel(); - for (int i=0; imData.mAttributeValues[i]; @@ -85,7 +84,7 @@ namespace } // skill bonus - for (int attribute=0; attribute (0.5 * (strength + endurance)) + multiplier * (creatureStats.getLevel() - 1)); } + + /** + * @brief autoCalculateSkills + * + * Skills are calculated with following formulae ( http://www.uesp.net/wiki/Morrowind:NPCs#Skills ): + * + * Skills: (Level - 1) × (Majority Multiplier + Specialization Multiplier) + * + * The Majority Multiplier is 1.0 for a Major or Minor Skill, or 0.1 for a Miscellaneous Skill. + * + * The Specialization Multiplier is 0.5 for a Skill in the same Specialization as the class, + * zero for other Skills. + * + * and by adding class, race, specialization bonus. + */ + void autoCalculateSkills(const ESM::NPC* npc, MWMechanics::NpcStats& npcStats) + { + const ESM::Class *class_ = + MWBase::Environment::get().getWorld()->getStore().get().find(npc->mClass); + + unsigned int level = npcStats.getLevel(); + + const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get().find(npc->mRace); + + + for (int i = 0; i < 2; ++i) + { + int bonus = (i==0) ? 10 : 25; + + for (int i2 = 0; i2 < 5; ++i2) + { + int index = class_->mData.mSkills[i2][i]; + if (index >= 0 && index < ESM::Skill::Length) + { + npcStats.getSkill(index).setBase (npcStats.getSkill(index).getBase() + bonus); + } + } + } + + for (int skillIndex = 0; skillIndex < ESM::Skill::Length; ++skillIndex) + { + float majorMultiplier = 0.1f; + float specMultiplier = 0.0f; + + int raceBonus = 0; + int specBonus = 0; + + for (int raceSkillIndex = 0; raceSkillIndex < 7; ++raceSkillIndex) + { + if (race->mData.mBonus[raceSkillIndex].mSkill == skillIndex) + { + raceBonus = race->mData.mBonus[raceSkillIndex].mBonus; + break; + } + } + + for (int k = 0; k < 5; ++k) + { + // is this a minor or major skill? + if ((class_->mData.mSkills[k][0] == skillIndex) || (class_->mData.mSkills[k][1] == skillIndex)) + { + majorMultiplier = 1.0f; + break; + } + } + + // is this skill in the same Specialization as the class? + const ESM::Skill* skill = MWBase::Environment::get().getWorld()->getStore().get().find(skillIndex); + if (skill->mData.mSpecialization == class_->mData.mSpecialization) + { + specMultiplier = 0.5f; + specBonus = 5; + } + + npcStats.getSkill(skillIndex).setBase( + std::min( + npcStats.getSkill(skillIndex).getBase() + + 5 + + raceBonus + + specBonus + + static_cast((level-1) * (majorMultiplier + specMultiplier)), 100.0f)); + } + } } namespace MWClass @@ -173,7 +255,7 @@ namespace MWClass { std::string faction = ref->mBase->mFaction; Misc::StringUtils::toLower(faction); - if(ref->mBase->mNpdt52.mGold != -10) + if(ref->mBase->mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) { data->mNpcStats.getFactionRanks()[faction] = (int)ref->mBase->mNpdt52.mRank; } @@ -185,11 +267,11 @@ namespace MWClass // creature stats int gold=0; - if(ref->mBase->mNpdt52.mGold != -10) + if(ref->mBase->mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) { gold = ref->mBase->mNpdt52.mGold; - for (int i=0; i<27; ++i) + for (unsigned int i=0; i< ESM::Skill::Length; ++i) data->mNpcStats.getSkill (i).setBase (ref->mBase->mNpdt52.mSkills[i]); data->mNpcStats.getAttribute(0).set (ref->mBase->mNpdt52.mStrength); @@ -220,6 +302,7 @@ namespace MWClass data->mNpcStats.setReputation(ref->mBase->mNpdt12.mReputation); autoCalculateAttributes(ref->mBase, data->mNpcStats); + autoCalculateSkills(ref->mBase, data->mNpcStats); } data->mNpcStats.getAiSequence().fill(ref->mBase->mAiPackage); diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index 0b36982890..1fdefc84f9 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -86,7 +86,7 @@ void MWMechanics::NpcStats::setMovementFlag (Flag flag, bool state) const MWMechanics::Stat& MWMechanics::NpcStats::getSkill (int index) const { - if (index<0 || index>=27) + if (index<0 || index>=ESM::Skill::Length) throw std::runtime_error ("skill index out of range"); return (!mIsWerewolf ? mSkill[index] : mWerewolfSkill[index]); @@ -94,7 +94,7 @@ const MWMechanics::Stat& MWMechanics::NpcStats::getSkill (int index) cons MWMechanics::Stat& MWMechanics::NpcStats::getSkill (int index) { - if (index<0 || index>=27) + if (index<0 || index>=ESM::Skill::Length) throw std::runtime_error ("skill index out of range"); return (!mIsWerewolf ? mSkill[index] : mWerewolfSkill[index]); diff --git a/components/esm/loadnpc.cpp b/components/esm/loadnpc.cpp index 9fff2d885d..e5b851bf0c 100644 --- a/components/esm/loadnpc.cpp +++ b/components/esm/loadnpc.cpp @@ -10,7 +10,7 @@ namespace ESM void NPC::load(ESMReader &esm) { - mNpdt52.mGold = -10; + //mNpdt52.mGold = -10; mPersistent = esm.getRecordFlags() & 0x0400; @@ -29,12 +29,12 @@ void NPC::load(ESMReader &esm) esm.getSubHeader(); if (esm.getSubSize() == 52) { - mNpdtType = 52; + mNpdtType = NPC_DEFAULT; esm.getExact(&mNpdt52, 52); } else if (esm.getSubSize() == 12) { - mNpdtType = 12; + mNpdtType = NPC_WITH_AUTOCALCULATED_STATS; esm.getExact(&mNpdt12, 12); } else @@ -76,9 +76,9 @@ void NPC::save(ESMWriter &esm) const esm.writeHNCString("KNAM", mHair); esm.writeHNOCString("SCRI", mScript); - if (mNpdtType == 52) + if (mNpdtType == NPC_DEFAULT) esm.writeHNT("NPDT", mNpdt52, 52); - else if (mNpdtType == 12) + else if (mNpdtType == NPC_WITH_AUTOCALCULATED_STATS) esm.writeHNT("NPDT", mNpdt12, 12); esm.writeHNT("FLAG", mFlags); @@ -114,7 +114,7 @@ void NPC::save(ESMWriter &esm) const mNpdt52.mLevel = 0; mNpdt52.mStrength = mNpdt52.mIntelligence = mNpdt52.mWillpower = mNpdt52.mAgility = mNpdt52.mSpeed = mNpdt52.mEndurance = mNpdt52.mPersonality = mNpdt52.mLuck = 0; - for (int i=0; i<27; ++i) mNpdt52.mSkills[i] = 0; + for (int i=0; i< Skill::Length; ++i) mNpdt52.mSkills[i] = 0; mNpdt52.mReputation = 0; mNpdt52.mHealth = mNpdt52.mMana = mNpdt52.mFatigue = 0; mNpdt52.mDisposition = 0; diff --git a/components/esm/loadnpc.hpp b/components/esm/loadnpc.hpp index d9e691669c..1eac8d64fe 100644 --- a/components/esm/loadnpc.hpp +++ b/components/esm/loadnpc.hpp @@ -8,6 +8,7 @@ #include "loadcont.hpp" #include "aipackage.hpp" #include "spelllist.hpp" +#include "loadskil.hpp" namespace ESM { @@ -58,6 +59,12 @@ struct NPC Metal = 0x0800 // Metal blood effect (golden?) }; + enum NpcType + { + NPC_WITH_AUTOCALCULATED_STATS = 12, + NPC_DEFAULT = 52 + }; + #pragma pack(push) #pragma pack(1) @@ -73,7 +80,7 @@ struct NPC mPersonality, mLuck; - char mSkills[27]; + char mSkills[Skill::Length]; char mReputation; short mHealth, mMana, mFatigue; char mDisposition, mFactionID, mRank; From 37a7ee8fcdd3adfc4406281ff9c2e83f3c7289b2 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 8 Dec 2013 23:05:21 +0100 Subject: [PATCH 412/434] Set alpha value of character animations according to Invisibility / Chameleon effects. --- apps/openmw/mwmechanics/character.cpp | 23 +++++++++ apps/openmw/mwmechanics/character.hpp | 2 + apps/openmw/mwrender/animation.hpp | 2 + apps/openmw/mwrender/npcanimation.cpp | 59 ++++++++++++++++++++++- apps/openmw/mwrender/npcanimation.hpp | 7 +++ apps/openmw/mwrender/renderingmanager.cpp | 48 +++++++++--------- apps/openmw/mwrender/renderingmanager.hpp | 4 +- components/nifogre/ogrenifloader.cpp | 11 +++-- files/materials/objects.shader | 8 --- 9 files changed, 125 insertions(+), 39 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 7505d34056..b70fcd0cc0 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -727,6 +727,8 @@ void CharacterController::update(float duration) const MWWorld::Class &cls = MWWorld::Class::get(mPtr); Ogre::Vector3 movement(0.0f); + updateVisibility(); + if(!cls.isActor()) { if(mAnimQueue.size() > 1) @@ -1130,4 +1132,25 @@ void CharacterController::updateContinuousVfx() } } +void CharacterController::updateVisibility() +{ + if (!mPtr.getClass().isActor()) + return; + float alpha = 1.f; + if (mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Invisibility).mMagnitude) + { + if (mPtr.getRefData().getHandle() == "player") + alpha = 0.4f; + else + alpha = 0.f; + } + float chameleon = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Chameleon).mMagnitude; + if (chameleon) + { + alpha *= std::max(0.2f, (100.f - chameleon)/100.f); + } + + mAnimation->setAlpha(alpha); +} + } diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 0b55534a60..9e07fca7d4 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -173,6 +173,8 @@ class CharacterController bool updateNpcState(bool onground, bool inwater, bool isrunning, bool sneak); + void updateVisibility(); + public: CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim); virtual ~CharacterController(); diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index b11b2b0a59..aa04e39e21 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -215,6 +215,8 @@ public: void addEffect (const std::string& model, int effectId, bool loop = false, const std::string& bonename = "", std::string texture = ""); void removeEffect (int effectId); void getLoopingEffects (std::vector& out); + + virtual void setAlpha(float alpha) {} private: void updateEffects(float duration); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index a57816f344..eb0c5dfbc8 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -5,6 +5,8 @@ #include #include +#include + #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/class.hpp" @@ -116,7 +118,8 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, int v mViewMode(viewMode), mShowWeapons(false), mShowShield(true), - mFirstPersonOffset(0.f, 0.f, 0.f) + mFirstPersonOffset(0.f, 0.f, 0.f), + mAlpha(1.f) { mNpc = mPtr.get()->mBase; @@ -219,6 +222,7 @@ void NpcAnimation::updateNpcBase() void NpcAnimation::updateParts() { + mAlpha = 1.f; const MWWorld::Class &cls = MWWorld::Class::get(mPtr); MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr); @@ -647,6 +651,7 @@ void NpcAnimation::showWeapons(bool showWeapon) { removeIndividualPart(ESM::PRT_Weapon); } + mAlpha = 1.f; } void NpcAnimation::showShield(bool show) @@ -705,4 +710,56 @@ void NpcAnimation::permanentEffectAdded(const ESM::MagicEffect *magicEffect, boo } } +void NpcAnimation::setAlpha(float alpha) +{ + if (alpha == mAlpha) + return; + mAlpha = alpha; + + for (int i=0; imEntities.size(); ++j) + { + Ogre::Entity* ent = mObjectParts[i]->mEntities[j]; + if (ent != mObjectParts[i]->mSkelBase) + applyAlpha(alpha, ent, mObjectParts[i]); + } + } +} + +void NpcAnimation::applyAlpha(float alpha, Ogre::Entity *ent, NifOgre::ObjectScenePtr scene) +{ + ent->getSubEntity(0)->setRenderQueueGroup(alpha != 1.f || ent->getSubEntity(0)->getMaterial()->isTransparent() + ? RQG_Alpha : RQG_Main); + + + Ogre::MaterialPtr mat = scene->mMaterialControllerMgr.getWritableMaterial(ent); + if (mAlpha == 1.f) + { + // Don't bother remembering what the original values were. Just remove the techniques and let the factory restore them. + mat->removeAllTechniques(); + sh::Factory::getInstance()._ensureMaterial(mat->getName(), "Default"); + return; + } + + Ogre::Material::TechniqueIterator techs = mat->getTechniqueIterator(); + while(techs.hasMoreElements()) + { + Ogre::Technique *tech = techs.getNext(); + Ogre::Technique::PassIterator passes = tech->getPassIterator(); + while(passes.hasMoreElements()) + { + Ogre::Pass *pass = passes.getNext(); + pass->setSceneBlending(Ogre::SBT_TRANSPARENT_ALPHA); + Ogre::ColourValue diffuse = pass->getDiffuse(); + diffuse.a = alpha; + pass->setDiffuse(diffuse); + pass->setVertexColourTracking(pass->getVertexColourTracking() &~Ogre::TVC_DIFFUSE); + } + } +} + } diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 9626632688..04dde87c7f 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -64,6 +64,8 @@ private: Ogre::SharedPtr mSayAnimationValue; + float mAlpha; + void updateNpcBase(); NifOgre::ObjectScenePtr insertBoundedPart(const std::string &model, int group, const std::string &bonename, @@ -78,6 +80,8 @@ private: void addPartGroup(int group, int priority, const std::vector &parts, bool enchantedGlow=false, Ogre::Vector3* glowColor=NULL); + void applyAlpha(float alpha, Ogre::Entity* ent, NifOgre::ObjectScenePtr scene); + public: /** * @param ptr @@ -109,6 +113,9 @@ public: /// Rebuilds the NPC, updating their root model, animation sources, and equipment. void rebuild(); + + /// Make the NPC only partially visible + virtual void setAlpha(float alpha); }; } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 26e88fb0dc..b216c789f7 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -60,14 +60,14 @@ RenderingManager::RenderingManager(OEngine::Render::OgreRenderer& _rend, const b MWWorld::Fallback* fallback) : mRendering(_rend) , mFallback(fallback) - , mObjects(mRendering) - , mActors(mRendering, this) , mPlayerAnimation(NULL) , mAmbientMode(0) , mSunEnabled(0) , mPhysicsEngine(engine) , mTerrain(NULL) { + mActors = new MWRender::Actors(mRendering, this); + mObjects = new MWRender::Objects(mRendering); // select best shader mode bool openGL = (Ogre::Root::getSingleton ().getRenderSystem ()->getName().find("OpenGL") != std::string::npos); bool glES = (Ogre::Root::getSingleton ().getRenderSystem ()->getName().find("OpenGL ES") != std::string::npos); @@ -162,8 +162,8 @@ RenderingManager::RenderingManager(OEngine::Render::OgreRenderer& _rend, const b mRootNode = mRendering.getScene()->getRootSceneNode(); mRootNode->createChildSceneNode("player"); - mObjects.setRootNode(mRootNode); - mActors.setRootNode(mRootNode); + mObjects->setRootNode(mRootNode); + mActors->setRootNode(mRootNode); mCamera = new MWRender::Camera(mRendering.getCamera()); @@ -201,6 +201,8 @@ RenderingManager::~RenderingManager () delete mCompositors; delete mWater; delete mVideoPlayer; + delete mActors; + delete mObjects; delete mFactory; } @@ -210,10 +212,10 @@ MWRender::SkyManager* RenderingManager::getSkyManager() } MWRender::Objects& RenderingManager::getObjects(){ - return mObjects; + return *mObjects; } MWRender::Actors& RenderingManager::getActors(){ - return mActors; + return *mActors; } OEngine::Render::Fader* RenderingManager::getFader() @@ -223,8 +225,8 @@ OEngine::Render::Fader* RenderingManager::getFader() void RenderingManager::removeCell (MWWorld::Ptr::CellStore *store) { - mObjects.removeCell(store); - mActors.removeCell(store); + mObjects->removeCell(store); + mActors->removeCell(store); mDebugging->cellRemoved(store); } @@ -240,7 +242,7 @@ void RenderingManager::toggleWater() void RenderingManager::cellAdded (MWWorld::Ptr::CellStore *store) { - mObjects.buildStaticGeometry (*store); + mObjects->buildStaticGeometry (*store); sh::Factory::getInstance().unloadUnreferencedMaterials(); mDebugging->cellAdded(store); waterAdded(store); @@ -254,8 +256,8 @@ void RenderingManager::addObject (const MWWorld::Ptr& ptr){ void RenderingManager::removeObject (const MWWorld::Ptr& ptr) { - if (!mObjects.deleteObject (ptr)) - mActors.deleteObject (ptr); + if (!mObjects->deleteObject (ptr)) + mActors->deleteObject (ptr); } void RenderingManager::moveObject (const MWWorld::Ptr& ptr, const Ogre::Vector3& position) @@ -295,9 +297,9 @@ RenderingManager::updateObjectCell(const MWWorld::Ptr &old, const MWWorld::Ptr & parent->removeChild(child); if (MWWorld::Class::get(old).isActor()) { - mActors.updateObjectCell(old, cur); + mActors->updateObjectCell(old, cur); } else { - mObjects.updateObjectCell(old, cur); + mObjects->updateObjectCell(old, cur); } } @@ -315,7 +317,7 @@ void RenderingManager::rebuildPtr(const MWWorld::Ptr &ptr) if(ptr.getRefData().getHandle() == "player") anim = mPlayerAnimation; else if(MWWorld::Class::get(ptr).isActor()) - anim = dynamic_cast(mActors.getAnimation(ptr)); + anim = dynamic_cast(mActors->getAnimation(ptr)); if(anim) { anim->rebuild(); @@ -380,8 +382,8 @@ void RenderingManager::update (float duration, bool paused) if(paused) return; - mActors.update (duration); - mObjects.update (duration); + mActors->update (duration); + mObjects->update (duration); mSkyManager->update(duration); @@ -657,7 +659,7 @@ void RenderingManager::requestMap(MWWorld::Ptr::CellStore* cell) { assert(mTerrain); - Ogre::AxisAlignedBox dims = mObjects.getDimensions(cell); + Ogre::AxisAlignedBox dims = mObjects->getDimensions(cell); Ogre::Vector2 center(cell->mCell->getGridX() + 0.5, cell->mCell->getGridY() + 0.5); dims.merge(mTerrain->getWorldBoundingBox(center)); @@ -667,7 +669,7 @@ void RenderingManager::requestMap(MWWorld::Ptr::CellStore* cell) mLocalMap->requestMap(cell, dims.getMinimum().z, dims.getMaximum().z); } else - mLocalMap->requestMap(cell, mObjects.getDimensions(cell)); + mLocalMap->requestMap(cell, mObjects->getDimensions(cell)); } void RenderingManager::preCellChange(MWWorld::Ptr::CellStore* cell) @@ -677,13 +679,13 @@ void RenderingManager::preCellChange(MWWorld::Ptr::CellStore* cell) void RenderingManager::disableLights(bool sun) { - mObjects.disableLights(); + mObjects->disableLights(); sunDisable(sun); } void RenderingManager::enableLights(bool sun) { - mObjects.enableLights(); + mObjects->enableLights(); sunEnable(sun); } @@ -859,7 +861,7 @@ void RenderingManager::processChangedSettings(const Settings::CategorySettingVec if (rebuild) { - mObjects.rebuildStaticGeometry(); + mObjects->rebuildStaticGeometry(); if (mTerrain) mTerrain->applyMaterials(Settings::Manager::getBool("enabled", "Shadows"), Settings::Manager::getBool("split", "Shadows")); @@ -976,13 +978,13 @@ void RenderingManager::setupExternalRendering (MWRender::ExternalRendering& rend Animation* RenderingManager::getAnimation(const MWWorld::Ptr &ptr) { - Animation *anim = mActors.getAnimation(ptr); + Animation *anim = mActors->getAnimation(ptr); if(!anim && ptr.getRefData().getHandle() == "player") anim = mPlayerAnimation; if (!anim) - anim = mObjects.getAnimation(ptr); + anim = mObjects->getAnimation(ptr); return anim; } diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 2d08139128..e5dcf0aebc 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -242,8 +242,8 @@ private: OEngine::Render::OgreRenderer &mRendering; - MWRender::Objects mObjects; - MWRender::Actors mActors; + MWRender::Objects* mObjects; + MWRender::Actors* mActors; MWRender::NpcAnimation *mPlayerAnimation; diff --git a/components/nifogre/ogrenifloader.cpp b/components/nifogre/ogrenifloader.cpp index 4819be4ca5..e6c535b9b8 100644 --- a/components/nifogre/ogrenifloader.cpp +++ b/components/nifogre/ogrenifloader.cpp @@ -63,11 +63,12 @@ Ogre::MaterialPtr MaterialControllerManager::getWritableMaterial(Ogre::MovableOb else if (Ogre::ParticleSystem* partSys = dynamic_cast(movable)) mat = Ogre::MaterialManager::getSingleton().getByName(partSys->getMaterialName()); - // Make sure techniques are created - sh::Factory::getInstance()._ensureMaterial(mat->getName(), "Default"); - static int count=0; - mat = mat->clone(mat->getName() + Ogre::StringConverter::toString(count++)); + Ogre::String newName = mat->getName() + Ogre::StringConverter::toString(count++); + sh::Factory::getInstance().createMaterialInstance(newName, mat->getName()); + // Make sure techniques are created + sh::Factory::getInstance()._ensureMaterial(newName, "Default"); + mat = Ogre::MaterialManager::getSingleton().getByName(newName); mClonedMaterials[movable] = mat; @@ -84,7 +85,7 @@ MaterialControllerManager::~MaterialControllerManager() { for (std::map::iterator it = mClonedMaterials.begin(); it != mClonedMaterials.end(); ++it) { - Ogre::MaterialManager::getSingleton().remove(it->second->getName()); + sh::Factory::getInstance().destroyMaterialInstance(it->second->getName()); } } diff --git a/files/materials/objects.shader b/files/materials/objects.shader index 5a3d872a5b..3d873f463f 100644 --- a/files/materials/objects.shader +++ b/files/materials/objects.shader @@ -98,9 +98,7 @@ #if VERTEXCOLOR_MODE != 2 shUniform(float4, materialAmbient) @shAutoConstant(materialAmbient, surface_ambient_colour) #endif -#if VERTEXCOLOR_MODE != 2 shUniform(float4, materialDiffuse) @shAutoConstant(materialDiffuse, surface_diffuse_colour) -#endif #if VERTEXCOLOR_MODE != 1 shUniform(float4, materialEmissive) @shAutoConstant(materialEmissive, surface_emissive_colour) #endif @@ -234,9 +232,7 @@ lightResult.xyz += lightAmbient.xyz * materialAmbient.xyz + materialEmissive.xyz; #endif -#if VERTEXCOLOR_MODE != 2 lightResult.a *= materialDiffuse.a; -#endif #endif } @@ -339,9 +335,7 @@ #if VERTEXCOLOR_MODE != 2 shUniform(float4, materialAmbient) @shAutoConstant(materialAmbient, surface_ambient_colour) #endif - #if VERTEXCOLOR_MODE != 2 shUniform(float4, materialDiffuse) @shAutoConstant(materialDiffuse, surface_diffuse_colour) - #endif #if VERTEXCOLOR_MODE != 1 shUniform(float4, materialEmissive) @shAutoConstant(materialEmissive, surface_emissive_colour) #endif @@ -434,9 +428,7 @@ lightResult.xyz += lightAmbient.xyz * materialAmbient.xyz + materialEmissive.xyz; #endif -#if VERTEXCOLOR_MODE != 2 lightResult.a *= materialDiffuse.a; -#endif #endif // shadows only for the first (directional) light From 5fd2df5546f6f33b1db5744c5946871e94a61c51 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 8 Dec 2013 23:21:23 +0100 Subject: [PATCH 413/434] Ignore invisible targets for combat AI --- apps/openmw/mwworld/worldimp.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 87a8d7d6f6..2f453f3987 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1845,6 +1845,13 @@ namespace MWWorld bool World::getLOS(const MWWorld::Ptr& npc,const MWWorld::Ptr& targetNpc) { + // This is a placeholder! Needs to go into an NPC awareness check function (see + // https://wiki.openmw.org/index.php?title=Research:NPC_AI_Behaviour#NPC_Awareness_Check ) + if (targetNpc.getClass().getCreatureStats(targetNpc).getMagicEffects().get(ESM::MagicEffect::Invisibility).mMagnitude) + return false; + if (targetNpc.getClass().getCreatureStats(targetNpc).getMagicEffects().get(ESM::MagicEffect::Chameleon).mMagnitude > 100) + return false; + Ogre::Vector3 halfExt1 = mPhysEngine->getCharacter(npc.getRefData().getHandle())->getHalfExtents(); float* pos1 = npc.getRefData().getPosition().pos; Ogre::Vector3 halfExt2 = mPhysEngine->getCharacter(targetNpc.getRefData().getHandle())->getHalfExtents(); From 0bc3a13c0f8f8f3242c5a7b7a1cf23bf5235b48b Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 8 Dec 2013 23:36:37 +0100 Subject: [PATCH 414/434] Break invisibility on Use or Activate --- apps/openmw/engine.cpp | 2 ++ apps/openmw/mwbase/world.hpp | 2 ++ apps/openmw/mwgui/statswindow.cpp | 2 +- apps/openmw/mwmechanics/character.cpp | 1 + apps/openmw/mwworld/inventorystore.cpp | 5 +++++ apps/openmw/mwworld/inventorystore.hpp | 5 ++++- apps/openmw/mwworld/worldimp.cpp | 6 ++++++ apps/openmw/mwworld/worldimp.hpp | 2 ++ 8 files changed, 23 insertions(+), 2 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 02e7a59550..f2afb3ba51 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -517,6 +517,8 @@ void OMW::Engine::activate() std::string script = MWWorld::Class::get (ptr).getScript (ptr); + MWBase::Environment::get().getWorld()->breakInvisibility(MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); + if (!script.empty()) { MWBase::Environment::get().getWorld()->getLocalScripts().setIgnore (ptr); diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index c092840dd8..8141af7124 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -419,6 +419,8 @@ namespace MWBase virtual void launchProjectile (const std::string& id, bool stack, const ESM::EffectList& effects, const MWWorld::Ptr& actor, const std::string& sourceName) = 0; + + virtual void breakInvisibility (const MWWorld::Ptr& actor) = 0; }; } diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index 9facdac404..d89abb4484 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -149,7 +149,7 @@ namespace MWGui // health, magicka, fatigue tooltip MyGUI::Widget* w; - std::string valStr = boost::lexical_cast(value.getCurrent()) + "/" + boost::lexical_cast(value.getModified()); + std::string valStr = boost::lexical_cast(int(value.getCurrent())) + "/" + boost::lexical_cast(int(value.getModified())); if (i==0) { getWidget(w, "Health"); diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index b70fcd0cc0..d7215ed13e 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -501,6 +501,7 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun { if(mUpperBodyState == UpperCharState_WeapEquiped) { + MWBase::Environment::get().getWorld()->breakInvisibility(mPtr); mAttackType.clear(); if(mWeaponType == WeapType_Spell) { diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 69e06378a6..57e35adce9 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -615,3 +615,8 @@ void MWWorld::InventoryStore::rechargeItems(float duration) it->second); } } + +void MWWorld::InventoryStore::purgeEffect(short effectId) +{ + mMagicEffects.add(MWMechanics::EffectKey(effectId), -mMagicEffects.get(MWMechanics::EffectKey(effectId)).mMagnitude); +} diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index 58ff50ead0..e764f64fb9 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -179,7 +179,10 @@ namespace MWWorld void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor); void rechargeItems (float duration); - /// Restore charge on enchanted items. Note this should only be done for the player. + ///< Restore charge on enchanted items. Note this should only be done for the player. + + void purgeEffect (short effectId); + ///< Remove a magic effect }; } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 2f453f3987..0bf41c7327 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2238,4 +2238,10 @@ namespace MWWorld deleteObject(movedPtr); } } + + void World::breakInvisibility(const Ptr &actor) + { + actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(ESM::MagicEffect::Invisibility); + actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Invisibility); + } } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 80119e014f..c8133441db 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -504,6 +504,8 @@ namespace MWWorld virtual void launchProjectile (const std::string& id, bool stack, const ESM::EffectList& effects, const MWWorld::Ptr& actor, const std::string& sourceName); + + virtual void breakInvisibility (const MWWorld::Ptr& actor); }; } From 57a33c957ea7575e6d8a27b11a1a9a2dbe3c9656 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 9 Dec 2013 14:26:08 +0100 Subject: [PATCH 415/434] Add possibly missing include --- components/nifogre/ogrenifloader.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/nifogre/ogrenifloader.hpp b/components/nifogre/ogrenifloader.hpp index 0fbc5b10eb..976a31ccd8 100644 --- a/components/nifogre/ogrenifloader.hpp +++ b/components/nifogre/ogrenifloader.hpp @@ -25,6 +25,7 @@ #define OPENMW_COMPONENTS_NIFOGRE_OGRENIFLOADER_HPP #include +#include #include #include From fc8bd1aacb9ae79f89fe37a7a1c7e409b7d6f3e5 Mon Sep 17 00:00:00 2001 From: Emanuel Guevel Date: Mon, 9 Dec 2013 21:13:06 +0100 Subject: [PATCH 416/434] Allow fatigue stat to become negative when fatigue damages are taken --- apps/openmw/mwclass/creature.cpp | 2 +- apps/openmw/mwclass/npc.cpp | 2 +- apps/openmw/mwmechanics/stat.hpp | 22 +++++++++++++++++----- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 9834807821..2e7f00dd33 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -199,7 +199,7 @@ namespace MWClass else { MWMechanics::DynamicStat fatigue(getCreatureStats(ptr).getFatigue()); - fatigue.setCurrent(fatigue.getCurrent() - damage); + fatigue.setCurrent(fatigue.getCurrent() - damage, true); getCreatureStats(ptr).setFatigue(fatigue); } } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index e7c10d3c81..5f9b2de478 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -669,7 +669,7 @@ namespace MWClass else { MWMechanics::DynamicStat fatigue(getCreatureStats(ptr).getFatigue()); - fatigue.setCurrent(fatigue.getCurrent() - damage); + fatigue.setCurrent(fatigue.getCurrent() - damage, true); getCreatureStats(ptr).setFatigue(fatigue); } } diff --git a/apps/openmw/mwmechanics/stat.hpp b/apps/openmw/mwmechanics/stat.hpp index 65d47c9c08..cb6c09014b 100644 --- a/apps/openmw/mwmechanics/stat.hpp +++ b/apps/openmw/mwmechanics/stat.hpp @@ -162,14 +162,26 @@ namespace MWMechanics setCurrent (getCurrent()+diff); } - void setCurrent (const T& value) + void setCurrent (const T& value, bool allowDecreaseBelowZero = false) { - mCurrent = value; + if (value > mCurrent) + { + // increase + mCurrent = value; - if (mCurrent<0) + if (mCurrent > getModified()) + mCurrent = getModified(); + } + else if (value > 0 || allowDecreaseBelowZero) + { + // allowed decrease + mCurrent = value; + } + else if (mCurrent > 0) + { + // capped decrease mCurrent = 0; - else if (mCurrent>getModified()) - mCurrent = getModified(); + } } void setModifier (const T& modifier) From 357ecd92b2c387dd7f7f93c91b0ff185df947cb3 Mon Sep 17 00:00:00 2001 From: Emanuel Guevel Date: Tue, 10 Dec 2013 00:41:36 +0100 Subject: [PATCH 417/434] Do not display negative stat values Display zero instead of negative values. Also remove useless for loops and some unused attributes. --- apps/openmw/mwgui/hud.cpp | 56 ++++++++++++-------------- apps/openmw/mwgui/statswindow.cpp | 50 +++++++++-------------- apps/openmw/mwgui/windowmanagerimp.cpp | 27 ------------- apps/openmw/mwgui/windowmanagerimp.hpp | 2 - 4 files changed, 45 insertions(+), 90 deletions(-) diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index f0843834d5..a78b1a6d1b 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -174,38 +174,32 @@ namespace MWGui void HUD::setValue(const std::string& id, const MWMechanics::DynamicStat& value) { - static const char *ids[] = - { - "HBar", "MBar", "FBar", 0 - }; + int current = std::max(0, static_cast(value.getCurrent())); + int modified = static_cast(value.getModified()); - for (int i=0; ids[i]; ++i) - if (ids[i]==id) - { - MyGUI::Widget* w; - std::string valStr = boost::lexical_cast(value.getCurrent()) + "/" + boost::lexical_cast(value.getModified()); - switch (i) - { - case 0: - mHealth->setProgressRange (value.getModified()); - mHealth->setProgressPosition (value.getCurrent()); - getWidget(w, "HealthFrame"); - w->setUserString("Caption_HealthDescription", "#{sHealthDesc}\n" + valStr); - break; - case 1: - mMagicka->setProgressRange (value.getModified()); - mMagicka->setProgressPosition (value.getCurrent()); - getWidget(w, "MagickaFrame"); - w->setUserString("Caption_HealthDescription", "#{sIntDesc}\n" + valStr); - break; - case 2: - mStamina->setProgressRange (value.getModified()); - mStamina->setProgressPosition (value.getCurrent()); - getWidget(w, "FatigueFrame"); - w->setUserString("Caption_HealthDescription", "#{sFatDesc}\n" + valStr); - break; - } - } + MyGUI::Widget* w; + std::string valStr = boost::lexical_cast(current) + "/" + boost::lexical_cast(modified); + if (id == "HBar") + { + mHealth->setProgressRange(modified); + mHealth->setProgressPosition(current); + getWidget(w, "HealthFrame"); + w->setUserString("Caption_HealthDescription", "#{sHealthDesc}\n" + valStr); + } + else if (id == "MBar") + { + mMagicka->setProgressRange (modified); + mMagicka->setProgressPosition (current); + getWidget(w, "MagickaFrame"); + w->setUserString("Caption_HealthDescription", "#{sIntDesc}\n" + valStr); + } + else if (id == "FBar") + { + mStamina->setProgressRange (modified); + mStamina->setProgressPosition (current); + getWidget(w, "FatigueFrame"); + w->setUserString("Caption_HealthDescription", "#{sFatDesc}\n" + valStr); + } } void HUD::setDrowningTimeLeft(float time) diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index 136328f57e..ab6096b7e6 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -134,38 +134,28 @@ namespace MWGui void StatsWindow::setValue (const std::string& id, const MWMechanics::DynamicStat& value) { - static const char *ids[] = - { - "HBar", "MBar", "FBar", - 0 - }; + int current = std::max(0, static_cast(value.getCurrent())); + int modified = static_cast(value.getModified()); - for (int i=0; ids[i]; ++i) - { - if (ids[i]==id) - { - std::string id (ids[i]); - setBar (id, id + "T", static_cast(value.getCurrent()), static_cast(value.getModified())); + setBar (id, id + "T", current, modified); - // health, magicka, fatigue tooltip - MyGUI::Widget* w; - std::string valStr = boost::lexical_cast(int(value.getCurrent())) + "/" + boost::lexical_cast(int(value.getModified())); - if (i==0) - { - getWidget(w, "Health"); - w->setUserString("Caption_HealthDescription", "#{sHealthDesc}\n" + valStr); - } - else if (i==1) - { - getWidget(w, "Magicka"); - w->setUserString("Caption_HealthDescription", "#{sIntDesc}\n" + valStr); - } - else if (i==2) - { - getWidget(w, "Fatigue"); - w->setUserString("Caption_HealthDescription", "#{sFatDesc}\n" + valStr); - } - } + // health, magicka, fatigue tooltip + MyGUI::Widget* w; + std::string valStr = boost::lexical_cast(current) + "/" + boost::lexical_cast(modified); + if (id == "HBar") + { + getWidget(w, "Health"); + w->setUserString("Caption_HealthDescription", "#{sHealthDesc}\n" + valStr); + } + else if (id == "MBar") + { + getWidget(w, "Magicka"); + w->setUserString("Caption_HealthDescription", "#{sIntDesc}\n" + valStr); + } + else if (id == "FBar") + { + getWidget(w, "Fatigue"); + w->setUserString("Caption_HealthDescription", "#{sFatDesc}\n" + valStr); } } diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 78986a0522..afa020082b 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -112,9 +112,6 @@ namespace MWGui , mPlayerMinorSkills() , mPlayerMajorSkills() , mPlayerSkillValues() - , mPlayerHealth() - , mPlayerMagicka() - , mPlayerFatigue() , mGui(NULL) , mGuiModes() , mCursorManager(NULL) @@ -590,32 +587,8 @@ namespace MWGui mStatsWindow->setValue (id, value); mHud->setValue (id, value); mCharGen->setValue(id, value); - if (id == "HBar") - { - mPlayerHealth = value; - } - else if (id == "MBar") - { - mPlayerMagicka = value; - } - else if (id == "FBar") - { - mPlayerFatigue = value; - } } - #if 0 - MWMechanics::DynamicStat WindowManager::getValue(const std::string& id) - { - if(id == "HBar") - return mPlayerHealth; - else if (id == "MBar") - return mPlayerMagicka; - else if (id == "FBar") - return mPlayerFatigue; - } - #endif - void WindowManager::setValue (const std::string& id, const std::string& value) { mStatsWindow->setValue (id, value); diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 4f19602958..743160aa82 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -346,8 +346,6 @@ namespace MWGui std::map > mPlayerAttributes; SkillList mPlayerMajorSkills, mPlayerMinorSkills; std::map > mPlayerSkillValues; - MWMechanics::DynamicStat mPlayerHealth, mPlayerMagicka, mPlayerFatigue; - MyGUI::Gui *mGui; // Gui std::vector mGuiModes; From 0c3c3ed8e95107745814f8fcfaa7b618544750ce Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 11 Dec 2013 15:15:30 +0100 Subject: [PATCH 418/434] Fix wind gravity affector --- components/nifogre/ogrenifloader.cpp | 2 +- components/nifogre/particles.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/nifogre/ogrenifloader.cpp b/components/nifogre/ogrenifloader.cpp index e6c535b9b8..acf8ac13af 100644 --- a/components/nifogre/ogrenifloader.cpp +++ b/components/nifogre/ogrenifloader.cpp @@ -724,7 +724,7 @@ class NIFObjectLoader { const Nif::NiMaterialColorController *matCtrl = dynamic_cast(ctrls.getPtr()); Ogre::ControllerValueRealPtr dstval(OGRE_NEW MaterialColorController::Value(movable, matCtrl->data.getPtr(), &scene->mMaterialControllerMgr)); - AlphaController::Function* function = OGRE_NEW AlphaController::Function(matCtrl, (animflags&Nif::NiNode::AnimFlag_AutoPlay)); + MaterialColorController::Function* function = OGRE_NEW MaterialColorController::Function(matCtrl, (animflags&Nif::NiNode::AnimFlag_AutoPlay)); scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); Ogre::ControllerFunctionRealPtr func(function); scene->mControllers.push_back(Ogre::Controller(srcval, dstval, func)); diff --git a/components/nifogre/particles.cpp b/components/nifogre/particles.cpp index 006a570dce..7b51f06679 100644 --- a/components/nifogre/particles.cpp +++ b/components/nifogre/particles.cpp @@ -763,7 +763,7 @@ public: protected: void applyWindForce(Ogre::ParticleSystem *psys, Ogre::Real timeElapsed) { - const Ogre::Vector3 vec = mDirection * mForce * timeElapsed; + const Ogre::Vector3 vec = mBone->_getDerivedOrientation() * mDirection * mForce * timeElapsed; Ogre::ParticleIterator pi = psys->_getIterator(); while (!pi.end()) { From 7a9b64c6f4823f16cd2f6e6a6de6812578c106d6 Mon Sep 17 00:00:00 2001 From: mrcheko Date: Thu, 12 Dec 2013 16:08:07 +0200 Subject: [PATCH 419/434] bug fix http://bugs.openmw.org/issues/985 --- apps/openmw/mwworld/actiontalk.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/actiontalk.cpp b/apps/openmw/mwworld/actiontalk.cpp index 905497f85b..6b5e274f57 100644 --- a/apps/openmw/mwworld/actiontalk.cpp +++ b/apps/openmw/mwworld/actiontalk.cpp @@ -1,15 +1,23 @@ #include "actiontalk.hpp" +#include "class.hpp" + #include "../mwbase/environment.hpp" #include "../mwbase/dialoguemanager.hpp" +#include "../mwmechanics/creaturestats.hpp" + namespace MWWorld { ActionTalk::ActionTalk (const Ptr& actor) : Action (false, actor) {} void ActionTalk::executeImp (const Ptr& actor) { - MWBase::Environment::get().getDialogueManager()->startDialogue (getTarget()); + MWWorld::Ptr talkTo = getTarget(); //because 'actor' is always the player! + if ( MWWorld::Class::get(talkTo).getCreatureStats(talkTo).isHostile() ) + return; + + MWBase::Environment::get().getDialogueManager()->startDialogue (talkTo); } } From 39eea24dc3866760cc40b79b6d57ebbc6799fc73 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 13 Dec 2013 03:50:01 +0100 Subject: [PATCH 420/434] Don't try to show exceptions in a message box if SDL was not initialized --- apps/openmw/main.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index b1bbb14f23..13e9d9241f 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -3,8 +3,7 @@ #include -#include -#include +#include #include "engine.hpp" #if defined(_WIN32) && !defined(_CONSOLE) @@ -282,7 +281,7 @@ int main(int argc, char**argv) } catch (std::exception &e) { - if (isatty(fileno(stdin))) + if (isatty(fileno(stdin)) || !SDL_WasInit(SDL_INIT_VIDEO)) std::cerr << "\nERROR: " << e.what() << std::endl; else SDL_ShowSimpleMessageBox(0, "OpenMW: Fatal error", e.what(), NULL); From c8bf69b91affbfd4082b24d70854ec15c878d260 Mon Sep 17 00:00:00 2001 From: mrcheko Date: Fri, 13 Dec 2013 19:02:25 +0200 Subject: [PATCH 421/434] Revert "bug fix http://bugs.openmw.org/issues/985" This reverts commit 7a9b64c6f4823f16cd2f6e6a6de6812578c106d6. --- apps/openmw/mwworld/actiontalk.cpp | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/apps/openmw/mwworld/actiontalk.cpp b/apps/openmw/mwworld/actiontalk.cpp index 6b5e274f57..905497f85b 100644 --- a/apps/openmw/mwworld/actiontalk.cpp +++ b/apps/openmw/mwworld/actiontalk.cpp @@ -1,23 +1,15 @@ #include "actiontalk.hpp" -#include "class.hpp" - #include "../mwbase/environment.hpp" #include "../mwbase/dialoguemanager.hpp" -#include "../mwmechanics/creaturestats.hpp" - namespace MWWorld { ActionTalk::ActionTalk (const Ptr& actor) : Action (false, actor) {} void ActionTalk::executeImp (const Ptr& actor) { - MWWorld::Ptr talkTo = getTarget(); //because 'actor' is always the player! - if ( MWWorld::Class::get(talkTo).getCreatureStats(talkTo).isHostile() ) - return; - - MWBase::Environment::get().getDialogueManager()->startDialogue (talkTo); + MWBase::Environment::get().getDialogueManager()->startDialogue (getTarget()); } } From 8b3a393a6b1b0a93cc7ac58353cbd197c3aaf346 Mon Sep 17 00:00:00 2001 From: mrcheko Date: Fri, 13 Dec 2013 19:33:01 +0200 Subject: [PATCH 422/434] bug fix at http://bugs.openmw.org/issues/985 --- apps/openmw/mwclass/npc.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index e7c10d3c81..5e9801c493 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -711,6 +711,8 @@ namespace MWClass return boost::shared_ptr(new MWWorld::ActionOpen(ptr, true)); if(get(actor).getStance(actor, MWWorld::Class::Sneak)) return boost::shared_ptr(new MWWorld::ActionOpen(ptr)); // stealing + if(get(ptr).getCreatureStats(ptr).isHostile()) + return boost::shared_ptr(new MWWorld::FailedAction("#{sActorInCombat}")); return boost::shared_ptr(new MWWorld::ActionTalk(ptr)); } From 530d06ab54352c79bca6c168f270168e8bcaa29b Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 14 Dec 2013 05:07:08 +0100 Subject: [PATCH 423/434] Remove unused code --- components/CMakeLists.txt | 4 +- components/files/filelibrary.cpp | 120 ------------------------------- components/files/filelibrary.hpp | 49 ------------- components/files/fileops.cpp | 120 ------------------------------- components/files/fileops.hpp | 38 ---------- 5 files changed, 2 insertions(+), 329 deletions(-) delete mode 100644 components/files/filelibrary.cpp delete mode 100644 components/files/filelibrary.hpp delete mode 100644 components/files/fileops.cpp delete mode 100644 components/files/fileops.hpp diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 50ac236c8a..a037fd5fa4 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -47,8 +47,8 @@ add_component_dir (misc ) add_component_dir (files - linuxpath windowspath macospath fixedpath multidircollection collections fileops configurationmanager - filelibrary constrainedfiledatastream lowlevelfile + linuxpath windowspath macospath fixedpath multidircollection collections configurationmanager + constrainedfiledatastream lowlevelfile ) add_component_dir (compiler diff --git a/components/files/filelibrary.cpp b/components/files/filelibrary.cpp deleted file mode 100644 index ce2d95f57c..0000000000 --- a/components/files/filelibrary.cpp +++ /dev/null @@ -1,120 +0,0 @@ -#include "filelibrary.hpp" - -#include - -#include -#include <../components/misc/stringops.hpp> - -namespace Files -{ - // Looks for a string in a vector of strings - bool containsVectorString(const StringVector& list, const std::string& str) - { - for (StringVector::const_iterator iter = list.begin(); - iter != list.end(); ++iter) - { - if (*iter == str) - return true; - } - return false; - } - - // Searches a path and adds the results to the library - void FileLibrary::add(const boost::filesystem::path &root, bool recursive, bool strict, - const StringVector &acceptableExtensions) - { - if (!boost::filesystem::exists(root)) - { - std::cout << "Warning " << root.string() << " does not exist.\n"; - return; - } - - std::string fileExtension; - std::string type; - - // remember the last location of the priority list when listing new items - int length = mPriorityList.size(); - - // First makes a list of all candidate files - FileLister(root, mPriorityList, recursive); - - // Then sort these files into sections according to the folder they belong to - for (PathContainer::iterator listIter = mPriorityList.begin() + length; - listIter != mPriorityList.end(); ++listIter) - { - if( !acceptableExtensions.empty() ) - { - fileExtension = boost::filesystem::path (listIter->extension()).string(); - Misc::StringUtils::toLower(fileExtension); - if(!containsVectorString(acceptableExtensions, fileExtension)) - continue; - } - - type = boost::filesystem::path (listIter->parent_path().leaf()).string(); - if (!strict) - Misc::StringUtils::toLower(type); - - mMap[type].push_back(*listIter); - // std::cout << "Added path: " << listIter->string() << " in section "<< type <second); - } - } - - // Searches the library for an item and returns a boost path to it - boost::filesystem::path FileLibrary::locate(std::string item, bool strict, bool ignoreExtensions, std::string sectionName) - { - boost::filesystem::path result(""); - if (sectionName == "") - { - return FileListLocator(mPriorityList, boost::filesystem::path(item), strict, ignoreExtensions); - } - else - { - if (!containsSection(sectionName, strict)) - { - std::cout << "Warning: There is no section named " << sectionName << "\n"; - return result; - } - result = FileListLocator(mMap[sectionName], boost::filesystem::path(item), strict, ignoreExtensions); - } - return result; - } - - // Prints all the available sections, used for debugging - void FileLibrary::printSections() - { - for(StringPathContMap::const_iterator mapIter = mMap.begin(); - mapIter != mMap.end(); ++mapIter) - { - std::cout << mapIter->first < - -namespace Files -{ - typedef std::map StringPathContMap; - typedef std::vector StringVector; - - /// Looks for a string in a vector of strings - bool containsVectorString(const StringVector& list, const std::string& str); - - /// \brief Searches directories and makes lists of files according to folder name - class FileLibrary - { - private: - StringPathContMap mMap; - PathContainer mEmptyPath; - PathContainer mPriorityList; - - public: - /// Searches a path and adds the results to the library - /// Recursive search and fs strict options are available - /// Takes a vector of acceptable files extensions, if none is given it lists everything. - void add(const boost::filesystem::path &root, bool recursive, bool strict, - const StringVector &acceptableExtensions); - - /// Returns true if the named section exists - /// You can run this check before running section() - bool containsSection(std::string sectionName, bool strict); - - /// Returns a pointer to const for a section of the library - /// which is essentially a PathContainer. - /// If the section does not exists it returns a pointer to an empty path. - const PathContainer* section(std::string sectionName, bool strict); - - /// Searches the library for an item and returns a boost path to it - /// Optionally you can provide a specific section - /// The result is the first that comes up according to alphabetical - /// section naming - boost::filesystem::path locate(std::string item, bool strict, bool ignoreExtensions, std::string sectionName=""); - - /// Prints all the available sections, used for debugging - void printSections(); - }; -} - -#endif diff --git a/components/files/fileops.cpp b/components/files/fileops.cpp deleted file mode 100644 index fbc2eef056..0000000000 --- a/components/files/fileops.cpp +++ /dev/null @@ -1,120 +0,0 @@ -#include "fileops.hpp" - -#include - -#include -#include -#include <../components/misc/stringops.hpp> - -namespace Files -{ - -bool isFile(const char *name) -{ - return boost::filesystem::exists(boost::filesystem::path(name)); -} - - // Returns true if the last part of the superset matches the subset - bool endingMatches(const std::string& superset, const std::string& subset) - { - if (subset.length() > superset.length()) - return false; - return superset.substr(superset.length() - subset.length()) == subset; - } - - // Makes a list of files from a directory - void FileLister( boost::filesystem::path currentPath, Files::PathContainer& list, bool recursive) - { - if (!boost::filesystem::exists(currentPath)) - { - std::cout << "WARNING: " << currentPath.string() << " does not exist.\n"; - return ; - } - if (recursive) - { - for ( boost::filesystem::recursive_directory_iterator end, itr(currentPath.string()); - itr != end; ++itr ) - { - if ( boost::filesystem::is_regular_file(*itr)) - list.push_back(itr->path()); - } - } - else - { - for ( boost::filesystem::directory_iterator end, itr(currentPath.string()); - itr != end; ++itr ) - { - if ( boost::filesystem::is_regular_file(*itr)) - list.push_back(itr->path()); - } - } - } - - // Locates path in path container - boost::filesystem::path FileListLocator (const Files::PathContainer& list, const boost::filesystem::path& toFind, - bool strict, bool ignoreExtensions) - { - boost::filesystem::path result(""); - if (list.empty()) - return result; - - std::string toFindStr; - if (ignoreExtensions) - toFindStr = boost::filesystem::basename(toFind); - else - toFindStr = toFind.string(); - - std::string fullPath; - - // The filesystems slash sets the default slash - std::string slash; - std::string wrongslash; - if(list[0].string().find("\\") != std::string::npos) - { - slash = "\\"; - wrongslash = "/"; - } - else - { - slash = "/"; - wrongslash = "\\"; - } - - // The file being looked for is converted to the new slash - if(toFindStr.find(wrongslash) != std::string::npos ) - { - boost::replace_all(toFindStr, wrongslash, slash); - } - - if (!strict) - { - Misc::StringUtils::toLower(toFindStr); - } - - for (Files::PathContainer::const_iterator it = list.begin(); it != list.end(); ++it) - { - fullPath = it->string(); - if (ignoreExtensions) - fullPath.erase(fullPath.length() - - boost::filesystem::path (it->extension()).string().length()); - - if (!strict) - { - Misc::StringUtils::toLower(fullPath); - } - if(endingMatches(fullPath, toFindStr)) - { - result = *it; - break; - } - } - return result; - } - - // Overloaded form of the locator that takes a string and returns a string - std::string FileListLocator (const Files::PathContainer& list,const std::string& toFind, bool strict, bool ignoreExtensions) - { - return FileListLocator(list, boost::filesystem::path(toFind), strict, ignoreExtensions).string(); - } - -} diff --git a/components/files/fileops.hpp b/components/files/fileops.hpp deleted file mode 100644 index bf1c51485f..0000000000 --- a/components/files/fileops.hpp +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef COMPONENTS_FILES_FILEOPS_HPP -#define COMPONENTS_FILES_FILEOPS_HPP - -#include -#include -#include - -#include - -namespace Files -{ - -///\brief Check if a given path is an existing file (not a directory) -///\param [in] name - filename -bool isFile(const char *name); - - /// A vector of Boost Paths, very handy - typedef std::vector PathContainer; - - /// Makes a list of files from a directory by taking a boost - /// path and a Path Container and adds to the Path container - /// all files in the path. It has a recursive option. - void FileLister( boost::filesystem::path currentPath, Files::PathContainer& list, bool recursive); - - /// Locates boost path in path container - /// returns the path from the container - /// that contains the searched path. - /// If it's not found it returns and empty path - /// Takes care of slashes, backslashes and it has a strict option. - boost::filesystem::path FileListLocator (const Files::PathContainer& list, const boost::filesystem::path& toFind, - bool strict, bool ignoreExtensions); - - /// Overloaded form of the locator that takes a string and returns a string - std::string FileListLocator (const Files::PathContainer& list,const std::string& toFind, bool strict, bool ignoreExtensions); - -} - -#endif /* COMPONENTS_FILES_FILEOPS_HPP */ From 4bc4af6bf0116ef1df832cca56395ea6023d7905 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 14 Dec 2013 22:01:24 +0100 Subject: [PATCH 424/434] Enable microcode caching for Ogre 1.9+ --- extern/shiny/Platforms/Ogre/OgrePlatform.cpp | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/extern/shiny/Platforms/Ogre/OgrePlatform.cpp b/extern/shiny/Platforms/Ogre/OgrePlatform.cpp index 3725d5f354..9f309fbcda 100644 --- a/extern/shiny/Platforms/Ogre/OgrePlatform.cpp +++ b/extern/shiny/Platforms/Ogre/OgrePlatform.cpp @@ -64,8 +64,11 @@ namespace sh bool OgrePlatform::supportsShaderSerialization () { - // Not very reliable in OpenGL mode (requires extension), and somehow doesn't work on linux even if the extension is present + #if OGRE_VERSION >= (1 << 16 | 9 << 8 | 0) + return true; + #else return Ogre::Root::getSingleton ().getRenderSystem ()->getName ().find("OpenGL") == std::string::npos; + #endif } bool OgrePlatform::supportsMaterialQueuedListener () @@ -110,10 +113,15 @@ namespace sh void OgrePlatform::serializeShaders (const std::string& file) { - std::fstream output; - output.open(file.c_str(), std::ios::out | std::ios::binary); - Ogre::DataStreamPtr shaderCache (OGRE_NEW Ogre::FileStreamDataStream(file, &output, false)); - Ogre::GpuProgramManager::getSingleton().saveMicrocodeCache(shaderCache); + #if OGRE_VERSION >= (1 << 16 | 9 << 8 | 0) + if (Ogre::GpuProgramManager::getSingleton().isCacheDirty()) + #endif + { + std::fstream output; + output.open(file.c_str(), std::ios::out | std::ios::binary); + Ogre::DataStreamPtr shaderCache (OGRE_NEW Ogre::FileStreamDataStream(file, &output, false)); + Ogre::GpuProgramManager::getSingleton().saveMicrocodeCache(shaderCache); + } } void OgrePlatform::deserializeShaders (const std::string& file) @@ -143,7 +151,7 @@ namespace sh else if (typeid(*value) == typeid(IntValue)) type = Ogre::GCT_INT1; else - assert(0); + throw std::runtime_error("unexpected type"); params->addConstantDefinition(name, type); mSharedParameters[name] = params; } From cd756a8a398d83034517cd1f5ae9d0ce0bb828fe Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 16 Dec 2013 13:22:27 +0100 Subject: [PATCH 425/434] Fix incorrect value for partially used items (missing float casts). Make sure the correct value is displayed in tooltips. --- apps/openmw/mwclass/apparatus.cpp | 2 +- apps/openmw/mwclass/armor.cpp | 4 ++-- apps/openmw/mwclass/book.cpp | 2 +- apps/openmw/mwclass/clothing.cpp | 2 +- apps/openmw/mwclass/ingredient.cpp | 2 +- apps/openmw/mwclass/light.cpp | 2 +- apps/openmw/mwclass/lockpick.cpp | 4 ++-- apps/openmw/mwclass/potion.cpp | 2 +- apps/openmw/mwclass/probe.cpp | 4 ++-- apps/openmw/mwclass/repair.cpp | 4 ++-- apps/openmw/mwclass/weapon.cpp | 4 ++-- 11 files changed, 16 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index 697b755792..53c62273dc 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -124,7 +124,7 @@ namespace MWClass std::string text; text += "\n#{sQuality}: " + MWGui::ToolTips::toString(ref->mBase->mData.mQuality); text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); + text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index f3f36542a1..5b2b7caa39 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -172,7 +172,7 @@ namespace MWClass if (ptr.getCellRef().mCharge == -1) return ref->mBase->mData.mValue; else - return ref->mBase->mData.mValue * (ptr.getCellRef().mCharge / getItemMaxHealth(ptr)); + return ref->mBase->mData.mValue * (static_cast(ptr.getCellRef().mCharge) / getItemMaxHealth(ptr)); } void Armor::registerSelf() @@ -248,7 +248,7 @@ namespace MWClass + MWGui::ToolTips::toString(ref->mBase->mData.mHealth); text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight) + " (" + typeText + ")"; - text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); + text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index b22cbc31fc..1da9209706 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -136,7 +136,7 @@ namespace MWClass std::string text; text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); + text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index 8941f36275..c162bbe9de 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -191,7 +191,7 @@ namespace MWClass std::string text; text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); + text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index 06d9d5d235..4296d4e1b6 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -145,7 +145,7 @@ namespace MWClass std::string text; text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); + text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index a031d25563..6a6133cb92 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -183,7 +183,7 @@ namespace MWClass std::string text; text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); + text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index 73b47d6af9..e1dc5b2e14 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -89,7 +89,7 @@ namespace MWClass if (ptr.getCellRef().mCharge == -1) return ref->mBase->mData.mValue; else - return ref->mBase->mData.mValue * (ptr.getCellRef().mCharge / getItemMaxHealth(ptr)); + return ref->mBase->mData.mValue * (static_cast(ptr.getCellRef().mCharge) / getItemMaxHealth(ptr)); } void Lockpick::registerSelf() @@ -141,7 +141,7 @@ namespace MWClass text += "\n#{sUses}: " + MWGui::ToolTips::toString(remainingUses); text += "\n#{sQuality}: " + MWGui::ToolTips::toString(ref->mBase->mData.mQuality); text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); + text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 883473eb33..e276c58aa5 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -128,7 +128,7 @@ namespace MWClass std::string text; text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); + text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); info.effects = MWGui::Widgets::MWEffectList::effectListFromESM(&ref->mBase->mEffects); diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index 845c2a0d00..b54464acdc 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -88,7 +88,7 @@ namespace MWClass if (ptr.getCellRef().mCharge == -1) return ref->mBase->mData.mValue; else - return ref->mBase->mData.mValue * (ptr.getCellRef().mCharge / getItemMaxHealth(ptr)); + return ref->mBase->mData.mValue * (static_cast(ptr.getCellRef().mCharge) / getItemMaxHealth(ptr)); } void Probe::registerSelf() @@ -140,7 +140,7 @@ namespace MWClass text += "\n#{sUses}: " + MWGui::ToolTips::toString(remainingUses); text += "\n#{sQuality}: " + MWGui::ToolTips::toString(ref->mBase->mData.mQuality); text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); + text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index dbfa9f0f62..ce2b4ff10c 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -79,7 +79,7 @@ namespace MWClass if (ptr.getCellRef().mCharge == -1) return ref->mBase->mData.mValue; else - return ref->mBase->mData.mValue * (ptr.getCellRef().mCharge / getItemMaxHealth(ptr)); + return ref->mBase->mData.mValue * (static_cast(ptr.getCellRef().mCharge) / getItemMaxHealth(ptr)); } void Repair::registerSelf() @@ -144,7 +144,7 @@ namespace MWClass text += "\n#{sUses}: " + MWGui::ToolTips::toString(remainingUses); text += "\n#{sQuality}: " + MWGui::ToolTips::toString(ref->mBase->mData.mQuality); text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); + text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { text += MWGui::ToolTips::getMiscString(ref->mRef.mOwner, "Owner"); diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index b1bf2b0b7f..a09e83380c 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -157,7 +157,7 @@ namespace MWClass if (ptr.getCellRef().mCharge == -1) return ref->mBase->mData.mValue; else - return ref->mBase->mData.mValue * (ptr.getCellRef().mCharge / getItemMaxHealth(ptr)); + return ref->mBase->mData.mValue * (static_cast(ptr.getCellRef().mCharge) / getItemMaxHealth(ptr)); } void Weapon::registerSelf() @@ -346,7 +346,7 @@ namespace MWClass } text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); + text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); info.enchant = ref->mBase->mEnchant; From 56893a097def1184301af4c635a119856f25850e Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 16 Dec 2013 13:31:03 +0100 Subject: [PATCH 426/434] Don't stack used torches --- apps/openmw/mwworld/class.cpp | 2 +- apps/openmw/mwworld/class.hpp | 2 +- apps/openmw/mwworld/containerstore.cpp | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index d3d1aff49b..ffe81a4ac8 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -139,7 +139,7 @@ namespace MWWorld float Class::getRemainingUsageTime (const Ptr& ptr) const { - throw std::runtime_error ("class does not support time-based uses"); + return -1; } std::string Class::getScript (const Ptr& ptr) const diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 2db293e688..cb6690a464 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -162,7 +162,7 @@ namespace MWWorld virtual float getRemainingUsageTime (const Ptr& ptr) const; ///< Returns the remaining duration of the object, such as an equippable light - /// source. (default implementation: throw an exception) + /// source. (default implementation: -1, i.e. infinite) virtual std::string getScript (const Ptr& ptr) const; ///< Return name of the script attached to ptr (default implementation: return an empty diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index d1d16ee01d..3797e69223 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -109,6 +109,8 @@ bool MWWorld::ContainerStore::stacks(const Ptr& ptr1, const Ptr& ptr2) && ptr1.getCellRef().mOwner == ptr2.getCellRef().mOwner && ptr1.getCellRef().mSoul == ptr2.getCellRef().mSoul + && ptr1.getClass().getRemainingUsageTime(ptr1) == ptr2.getClass().getRemainingUsageTime(ptr2) + && cls1.getScript(ptr1) == cls2.getScript(ptr2) // item that is already partly used up never stacks From eb5e4ecec2f15dcc6ec22b12159d49b686c59427 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 16 Dec 2013 15:35:06 +0100 Subject: [PATCH 427/434] Remove more unused code --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwbase/world.hpp | 1 - apps/openmw/mwgui/loadingscreen.cpp | 36 +------- apps/openmw/mwrender/compositors.cpp | 108 ---------------------- apps/openmw/mwrender/compositors.hpp | 64 ------------- apps/openmw/mwrender/renderingmanager.cpp | 42 +-------- apps/openmw/mwrender/renderingmanager.hpp | 6 -- apps/openmw/mwrender/water.hpp | 2 - 8 files changed, 7 insertions(+), 254 deletions(-) delete mode 100644 apps/openmw/mwrender/compositors.cpp delete mode 100644 apps/openmw/mwrender/compositors.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 90fdd11ba9..5a062575c7 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -19,7 +19,7 @@ source_group(game FILES ${GAME} ${GAME_HEADER}) add_openmw_dir (mwrender renderingmanager debugging sky camera animation npcanimation creatureanimation activatoranimation actors objects renderinginterface localmap occlusionquery water shadows - compositors characterpreview externalrendering globalmap videoplayer ripplesimulation refraction + characterpreview externalrendering globalmap videoplayer ripplesimulation refraction terrainstorage ) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 8141af7124..961d3d9587 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -80,7 +80,6 @@ namespace MWBase Render_CollisionDebug, Render_Wireframe, Render_Pathgrid, - Render_Compositors, Render_BoundingBoxes }; diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 4bd383c2f3..868b582096 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -1,8 +1,6 @@ #include "loadingscreen.hpp" #include -#include -#include #include @@ -199,28 +197,7 @@ namespace MWGui MWBase::Environment::get().getInputManager()->update(0, true); - Ogre::CompositorChain* chain = Ogre::CompositorManager::getSingleton().getCompositorChain(mWindow->getViewport(0)); - - bool hasCompositor = chain->getCompositor ("gbufferFinalizer"); - - - if (!hasCompositor) - { - mWindow->getViewport(0)->setClearEveryFrame(false); - } - else - { - if (!mFirstLoad) - { - mBackgroundMaterial->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureName(chain->getCompositor ("gbufferFinalizer")->getTextureInstance ("no_mrt_output", 0)->getName()); - mRectangle->setVisible(true); - } - - for (unsigned int i = 0; igetNumCompositors(); ++i) - { - Ogre::CompositorManager::getSingleton().setCompositorEnabled(mWindow->getViewport(0), chain->getCompositor(i)->getCompositor()->getName(), false); - } - } + mWindow->getViewport(0)->setClearEveryFrame(false); // First, swap buffers from last draw, then, queue an update of the // window contents, but don't swap buffers (which would have @@ -231,15 +208,8 @@ namespace MWGui mWindow->update(false); - if (!hasCompositor) - mWindow->getViewport(0)->setClearEveryFrame(true); - else - { - for (unsigned int i = 0; igetNumCompositors(); ++i) - { - Ogre::CompositorManager::getSingleton().setCompositorEnabled(mWindow->getViewport(0), chain->getCompositor(i)->getCompositor()->getName(), true); - } - } + mWindow->getViewport(0)->setClearEveryFrame(true); + mRectangle->setVisible(false); diff --git a/apps/openmw/mwrender/compositors.cpp b/apps/openmw/mwrender/compositors.cpp deleted file mode 100644 index b1c98a3067..0000000000 --- a/apps/openmw/mwrender/compositors.cpp +++ /dev/null @@ -1,108 +0,0 @@ -#include "compositors.hpp" - -#include -#include -#include -#include - -using namespace MWRender; - -Compositors::Compositors(Ogre::Viewport* vp) : - mViewport(vp) - , mEnabled(true) -{ -} - -Compositors::~Compositors() -{ - Ogre::CompositorManager::getSingleton().removeCompositorChain(mViewport); -} - -void Compositors::setEnabled (const bool enabled) -{ - for (CompositorMap::iterator it=mCompositors.begin(); - it != mCompositors.end(); ++it) - { - Ogre::CompositorManager::getSingleton().setCompositorEnabled(mViewport, it->first, enabled && it->second.first); - } - mEnabled = enabled; -} - -void Compositors::recreate() -{ - Ogre::CompositorManager::getSingleton().removeCompositorChain(mViewport); - - CompositorMap temp = mCompositors; - mCompositors.clear(); - - for (CompositorMap::iterator it=temp.begin(); - it != temp.end(); ++it) - { - addCompositor(it->first, it->second.second); - setCompositorEnabled(it->first, mEnabled && it->second.first); - } -} - -void Compositors::addCompositor (const std::string& name, const int priority) -{ - int id = 0; - - for (CompositorMap::iterator it=mCompositors.begin(); - it != mCompositors.end(); ++it) - { - if (it->second.second > priority) - break; - ++id; - } - Ogre::CompositorManager::getSingleton().addCompositor (mViewport, name, id); - - mCompositors[name] = std::make_pair(false, priority); -} - -void Compositors::setCompositorEnabled (const std::string& name, const bool enabled) -{ - mCompositors[name].first = enabled; - Ogre::CompositorManager::getSingleton().setCompositorEnabled (mViewport, name, enabled && mEnabled); -} - -void Compositors::removeAll() -{ - Ogre::CompositorManager::getSingleton().removeCompositorChain(mViewport); - - mCompositors.clear(); -} - -bool Compositors::anyCompositorEnabled() -{ - for (CompositorMap::iterator it=mCompositors.begin(); - it != mCompositors.end(); ++it) - { - if (it->second.first && mEnabled) - return true; - } - return false; -} - -void Compositors::countTrianglesBatches(unsigned int &triangles, unsigned int &batches) -{ - triangles = 0; - batches = 0; - - Ogre::CompositorInstance* c = NULL; - Ogre::CompositorChain* chain = Ogre::CompositorManager::getSingleton().getCompositorChain (mViewport); - // accumulate tris & batches from all compositors with all their render targets - for (unsigned int i=0; i < chain->getNumCompositors(); ++i) - { - if (chain->getCompositor(i)->getEnabled()) - { - c = chain->getCompositor(i); - for (unsigned int j = 0; j < c->getTechnique()->getNumTargetPasses(); ++j) - { - std::string textureName = c->getTechnique()->getTargetPass(j)->getOutputName(); - Ogre::RenderTarget* rt = c->getRenderTarget(textureName); - triangles += rt->getTriangleCount(); - batches += rt->getBatchCount(); - } - } - } -} diff --git a/apps/openmw/mwrender/compositors.hpp b/apps/openmw/mwrender/compositors.hpp deleted file mode 100644 index e5dd7503ce..0000000000 --- a/apps/openmw/mwrender/compositors.hpp +++ /dev/null @@ -1,64 +0,0 @@ -#ifndef GAME_MWRENDER_COMPOSITORS_H -#define GAME_MWRENDER_COMPOSITORS_H - -#include -#include - -namespace Ogre -{ - class Viewport; -} - -namespace MWRender -{ - typedef std::map < std::string, std::pair > CompositorMap; - - /// \brief Manages a set of compositors for one viewport - class Compositors - { - public: - Compositors(Ogre::Viewport* vp); - virtual ~Compositors(); - - /** - * enable or disable all compositors globally - */ - void setEnabled (const bool enabled); - - void setViewport(Ogre::Viewport* vp) { mViewport = vp; } - - /// recreate compositors (call this after viewport size changes) - void recreate(); - - bool toggle() { setEnabled(!mEnabled); return mEnabled; } - - /** - * enable or disable a specific compositor - * @note enable has no effect if all compositors are globally disabled - */ - void setCompositorEnabled (const std::string& name, const bool enabled); - - /** - * @param name of compositor - * @param priority, lower number will be first in the chain - */ - void addCompositor (const std::string& name, const int priority); - - bool anyCompositorEnabled(); - - void countTrianglesBatches(unsigned int &triangles, unsigned int &batches); - - void removeAll (); - - protected: - /// maps compositor name to its "enabled" state - CompositorMap mCompositors; - - bool mEnabled; - - Ogre::Viewport* mViewport; - }; - -} - -#endif diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index b216c789f7..0b10791b8b 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -8,10 +8,6 @@ #include #include #include -#include -#include -#include -#include #include #include #include @@ -43,7 +39,6 @@ #include "shadows.hpp" #include "localmap.hpp" #include "water.hpp" -#include "compositors.hpp" #include "npcanimation.hpp" #include "externalrendering.hpp" #include "globalmap.hpp" @@ -87,8 +82,6 @@ RenderingManager::RenderingManager(OEngine::Render::OgreRenderer& _rend, const b mRendering.getWindow()->addListener(this); mRendering.setWindowListener(this); - mCompositors = new Compositors(mRendering.getViewport()); - mWater = 0; // material system @@ -157,8 +150,6 @@ RenderingManager::RenderingManager(OEngine::Render::OgreRenderer& _rend, const b sh::Factory::getInstance ().setGlobalSetting ("viewproj_fix", "false"); sh::Factory::getInstance ().setSharedParameter ("vpRow2Fix", sh::makeProperty (new sh::Vector4(0,0,0,0))); - applyCompositors(); - mRootNode = mRendering.getScene()->getRootSceneNode(); mRootNode->createChildSceneNode("player"); @@ -198,7 +189,6 @@ RenderingManager::~RenderingManager () delete mTerrain; delete mLocalMap; delete mOcclusionQuery; - delete mCompositors; delete mWater; delete mVideoPlayer; delete mActors; @@ -478,29 +468,21 @@ bool RenderingManager::toggleRenderMode(int mode) { if (mRendering.getCamera()->getPolygonMode() == PM_SOLID) { - mCompositors->setEnabled(false); - mRendering.getCamera()->setPolygonMode(PM_WIREFRAME); return true; } else { - mCompositors->setEnabled(true); - mRendering.getCamera()->setPolygonMode(PM_SOLID); return false; } } - else if (mode == MWBase::World::Render_BoundingBoxes) + else //if (mode == MWBase::World::Render_BoundingBoxes) { bool show = !mRendering.getScene()->getShowBoundingBoxes(); mRendering.getScene()->showBoundingBoxes(show); return show; } - else //if (mode == MWBase::World::Render_Compositors) - { - return mCompositors->toggle(); - } } void RenderingManager::configureFog(MWWorld::Ptr::CellStore &mCell) @@ -745,11 +727,6 @@ Ogre::Vector4 RenderingManager::boundingBoxToScreen(Ogre::AxisAlignedBox bounds) return Vector4(min_x, min_y, max_x, max_y); } -Compositors* RenderingManager::getCompositors() -{ - return mCompositors; -} - void RenderingManager::processChangedSettings(const Settings::CategorySettingVector& settings) { bool changeRes = false; @@ -795,7 +772,6 @@ void RenderingManager::processChangedSettings(const Settings::CategorySettingVec } else if (it->second == "shader" && it->first == "Water") { - applyCompositors(); sh::Factory::getInstance ().setGlobalSetting ("simple_water", Settings::Manager::getBool("shader", "Water") ? "false" : "true"); rebuild = true; mRendering.getViewport ()->setClearEveryFrame (true); @@ -883,28 +859,16 @@ void RenderingManager::windowResized(int x, int y) Settings::Manager::setInt("resolution x", "Video", x); Settings::Manager::setInt("resolution y", "Video", y); mRendering.adjustViewport(); - mCompositors->recreate(); mVideoPlayer->setResolution (x, y); MWBase::Environment::get().getWindowManager()->windowResized(x,y); } -void RenderingManager::applyCompositors() -{ -} - void RenderingManager::getTriangleBatchCount(unsigned int &triangles, unsigned int &batches) { - if (mCompositors->anyCompositorEnabled()) - { - mCompositors->countTrianglesBatches(triangles, batches); - } - else - { - triangles = mRendering.getWindow()->getTriangleCount(); - batches = mRendering.getWindow()->getBatchCount(); - } + batches = mRendering.getWindow()->getBatchCount(); + triangles = mRendering.getWindow()->getTriangleCount(); } void RenderingManager::setupPlayer(const MWWorld::Ptr &ptr) diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index e5dcf0aebc..abc8fd71a7 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -48,7 +48,6 @@ namespace MWRender class Shadows; class LocalMap; class Water; - class Compositors; class ExternalRendering; class GlobalMap; class VideoPlayer; @@ -96,7 +95,6 @@ public: void renderPlayer(const MWWorld::Ptr &ptr); SkyManager* getSkyManager(); - Compositors* getCompositors(); void toggleLight(); bool toggleRenderMode(int mode); @@ -224,8 +222,6 @@ private: void setMenuTransparency(float val); - void applyCompositors(); - bool mSunEnabled; MWWorld::Fallback* mFallback; @@ -269,8 +265,6 @@ private: MWRender::Shadows* mShadows; - MWRender::Compositors* mCompositors; - VideoPlayer* mVideoPlayer; }; diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp index bc15b4980a..481a412977 100644 --- a/apps/openmw/mwrender/water.hpp +++ b/apps/openmw/mwrender/water.hpp @@ -133,8 +133,6 @@ namespace MWRender { RenderingManager* mRendering; SkyManager* mSky; - std::string mCompositorName; - Ogre::MaterialPtr mMaterial; bool mUnderwaterEffect; From da9b67b6d22b1e34ae7f3895f424a75fae04bd34 Mon Sep 17 00:00:00 2001 From: pvdk Date: Mon, 16 Dec 2013 20:40:58 +0100 Subject: [PATCH 428/434] Fix for Bug #922: Launcher writing merged openmw.cfg files --- apps/launcher/maindialog.cpp | 26 ++++++++++++++++ apps/launcher/settings/gamesettings.cpp | 41 ++++++++++++++++--------- apps/launcher/settings/gamesettings.hpp | 11 +++++++ 3 files changed, 63 insertions(+), 15 deletions(-) diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 4012a1fbd5..a6ac3d78d7 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -443,6 +443,32 @@ bool Launcher::MainDialog::setupGameSettings() QString userPath = QString::fromStdString(mCfgMgr.getUserPath().string()); QString globalPath = QString::fromStdString(mCfgMgr.getGlobalPath().string()); + // Load the user config file first, separately + // So we can write it properly, uncontaminated + QString path = userPath + QLatin1String("openmw.cfg"); + QFile file(path); + + qDebug() << "Loading config file:" << qPrintable(path); + + if (file.exists()) { + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Error opening OpenMW configuration file")); + msgBox.setIcon(QMessageBox::Critical); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setText(QObject::tr("
Could not open %0 for reading

\ + Please make sure you have the right permissions \ + and try again.
").arg(file.fileName())); + msgBox.exec(); + return false; + } + QTextStream stream(&file); + stream.setCodec(QTextCodec::codecForName("UTF-8")); + + mGameSettings.readUserFile(stream); + } + + // Now the rest QStringList paths; paths.append(userPath + QString("openmw.cfg")); paths.append(QString("openmw.cfg")); diff --git a/apps/launcher/settings/gamesettings.cpp b/apps/launcher/settings/gamesettings.cpp index 41113c35aa..e7e5cf1ea7 100644 --- a/apps/launcher/settings/gamesettings.cpp +++ b/apps/launcher/settings/gamesettings.cpp @@ -90,6 +90,16 @@ QStringList Launcher::GameSettings::values(const QString &key, const QStringList } bool Launcher::GameSettings::readFile(QTextStream &stream) +{ + return readFile(stream, mSettings); +} + +bool Launcher::GameSettings::readUserFile(QTextStream &stream) +{ + return readFile(stream, mUserSettings); +} + +bool Launcher::GameSettings::readFile(QTextStream &stream, QMap &settings) { QMap cache; QRegExp keyRe("^([^=]+)\\s*=\\s*(.+)$"); @@ -107,10 +117,10 @@ bool Launcher::GameSettings::readFile(QTextStream &stream) // Don't remove existing data entries if (key != QLatin1String("data")) - mSettings.remove(key); + settings.remove(key); QStringList values = cache.values(key); - values.append(mSettings.values(key)); + values.append(settings.values(key)); if (!values.contains(value)) { cache.insertMulti(key, value); @@ -118,23 +128,24 @@ bool Launcher::GameSettings::readFile(QTextStream &stream) } } - if (mSettings.isEmpty()) { - mSettings = cache; // This is the first time we read a file + if (settings.isEmpty()) { + settings = cache; // This is the first time we read a file validatePaths(); return true; } // Merge the changed keys with those which didn't - mSettings.unite(cache); + settings.unite(cache); validatePaths(); return true; } + bool Launcher::GameSettings::writeFile(QTextStream &stream) { // Iterate in reverse order to preserve insertion order - QMapIterator i(mSettings); + QMapIterator i(mUserSettings); i.toBack(); while (i.hasPrevious()) { @@ -162,7 +173,7 @@ bool Launcher::GameSettings::writeFile(QTextStream &stream) } - QStringList content = mSettings.values(QString("content")); + QStringList content = mUserSettings.values(QString("content")); for (int i = content.count(); i--;) { stream << "content=" << content.at(i) << "\n"; } @@ -172,14 +183,14 @@ bool Launcher::GameSettings::writeFile(QTextStream &stream) bool Launcher::GameSettings::hasMaster() { - bool result = false; - QStringList content = mSettings.values(QString("content")); - for (int i = 0; i < content.count(); ++i) { - if (content.at(i).contains(".omwgame") || content.at(i).contains(".esm")) { - result = true; - break; + bool result = false; + QStringList content = mSettings.values(QString("content")); + for (int i = 0; i < content.count(); ++i) { + if (content.at(i).contains(".omwgame") || content.at(i).contains(".esm")) { + result = true; + break; + } } - } - return result; + return result; } diff --git a/apps/launcher/settings/gamesettings.hpp b/apps/launcher/settings/gamesettings.hpp index 60236200a9..df82150742 100644 --- a/apps/launcher/settings/gamesettings.hpp +++ b/apps/launcher/settings/gamesettings.hpp @@ -31,6 +31,7 @@ namespace Launcher inline void setValue(const QString &key, const QString &value) { mSettings.insert(key, value); + mUserSettings.insert(key, value); } inline void setMultiValue(const QString &key, const QString &value) @@ -38,11 +39,16 @@ namespace Launcher QStringList values = mSettings.values(key); if (!values.contains(value)) mSettings.insertMulti(key, value); + + values = mUserSettings.values(key); + if (!values.contains(value)) + mUserSettings.insertMulti(key, value); } inline void remove(const QString &key) { mSettings.remove(key); + mUserSettings.remove(key); } inline QStringList getDataDirs() { return mDataDirs; } @@ -52,7 +58,11 @@ namespace Launcher bool hasMaster(); QStringList values(const QString &key, const QStringList &defaultValues = QStringList()); + bool readFile(QTextStream &stream); + bool readFile(QTextStream &stream, QMap &settings); + bool readUserFile(QTextStream &stream); + bool writeFile(QTextStream &stream); private: @@ -60,6 +70,7 @@ namespace Launcher void validatePaths(); QMap mSettings; + QMap mUserSettings; QStringList mDataDirs; QString mDataLocal; From 876fb9a899cf19a045b0b9a63be6144f1f1c0daa Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 19 Dec 2013 00:11:14 +0100 Subject: [PATCH 429/434] Addition to bb4bd999ba706e : adjust position for objects placed from inventory --- apps/openmw/mwworld/worldimp.cpp | 8 ++++---- apps/openmw/mwworld/worldimp.hpp | 3 +-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index f64d221223..448211bc2e 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1095,7 +1095,7 @@ namespace MWWorld MWWorld::Ptr World::safePlaceObject(const MWWorld::Ptr& ptr,MWWorld::CellStore &Cell,ESM::Position pos) { - return copyObjectToCell(ptr,Cell,pos); + return copyObjectToCell(ptr,Cell,pos,false); } void World::indexToPosition (int cellX, int cellY, float &x, float &y, bool centre) const @@ -1516,7 +1516,7 @@ namespace MWWorld // copy the object and set its count int origCount = object.getRefData().getCount(); object.getRefData().setCount(amount); - Ptr dropped = copyObjectToCell(object, *cell, pos); + Ptr dropped = copyObjectToCell(object, *cell, pos, true); object.getRefData().setCount(origCount); // only the player place items in the world, so no need to check actor @@ -1537,13 +1537,13 @@ namespace MWWorld } - Ptr World::copyObjectToCell(const Ptr &object, CellStore &cell, const ESM::Position &pos) + Ptr World::copyObjectToCell(const Ptr &object, CellStore &cell, const ESM::Position &pos, bool adjustPos) { /// \todo add searching correct cell for position specified MWWorld::Ptr dropped = MWWorld::Class::get(object).copyToCell(object, cell, pos); - if (object.getClass().isActor()) + if (object.getClass().isActor() || adjustPos) { Ogre::Vector3 min, max; if (mPhysics->getObjectAABB(object, min, max)) { diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index c8133441db..5a51cb773c 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -114,8 +114,7 @@ namespace MWWorld bool moveObjectImp (const Ptr& ptr, float x, float y, float z); ///< @return true if the active cell (cell player is in) changed - - Ptr copyObjectToCell(const Ptr &ptr, CellStore &cell, const ESM::Position &pos); + Ptr copyObjectToCell(const Ptr &ptr, CellStore &cell, const ESM::Position &pos, bool adjustPos=true); void updateWindowManager (); void performUpdateSceneQueries (); From a0d38dfb6371d3eeb2da5aa1e82a781ec5d48022 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 19 Dec 2013 00:26:08 +0100 Subject: [PATCH 430/434] Fix highlighted topics being selectable when in a choice --- apps/openmw/mwdialogue/dialoguemanagerimp.cpp | 6 ++---- apps/openmw/mwgui/dialogue.cpp | 2 ++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 52493bf765..3951cedcbc 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -413,10 +413,6 @@ namespace MWDialogue void DialogueManager::goodbyeSelected() { - // Do not close the dialogue window if the player has to answer a question - if (mIsInChoice) - return; - MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Dialogue); // Apply disposition change to NPC's base disposition @@ -474,6 +470,8 @@ namespace MWDialogue void DialogueManager::goodbye() { + mIsInChoice = true; + MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow(); win->goodbye(); diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index 71995f97fd..914302d84e 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -302,6 +302,8 @@ namespace MWGui void DialogueWindow::onByeClicked(MyGUI::Widget* _sender) { + if (!mEnabled || MWBase::Environment::get().getDialogueManager()->isInChoice()) + return; MWBase::Environment::get().getDialogueManager()->goodbyeSelected(); } From 9afdf71af30c42e6162532fa1bb9ee20868b459a Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 19 Dec 2013 00:37:57 +0100 Subject: [PATCH 431/434] Fix crash with player->position command --- apps/openmw/mwworld/scene.cpp | 3 +-- apps/openmw/mwworld/scene.hpp | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 25ce038f36..dab272f7c0 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -203,6 +203,7 @@ namespace MWWorld void Scene::changeCell (int X, int Y, const ESM::Position& position, bool adjustPlayerPos) { + mRendering.enableTerrain(true); Nif::NIFFile::CacheLock cachelock; Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); @@ -436,8 +437,6 @@ namespace MWWorld MWBase::Environment::get().getWorld()->positionToIndex (position.pos[0], position.pos[1], x, y); - mRendering.enableTerrain(true); - changeCell (x, y, position, true); } diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index e3edad352a..73c3c4b126 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -71,8 +71,6 @@ namespace MWWorld void loadCell (CellStore *cell, Loading::Listener* loadingListener); void changeCell (int X, int Y, const ESM::Position& position, bool adjustPlayerPos); - ///< Move from exterior to interior or from interior cell to a different - /// interior cell. CellStore* getCurrentCell (); From 6a3eb3b355ecff7983ad61eeaad01fb576568e3f Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 19 Dec 2013 01:31:12 +0100 Subject: [PATCH 432/434] Better way of reversing layer UV. --- files/materials/terrain.shader | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/files/materials/terrain.shader b/files/materials/terrain.shader index eda80c9e30..7903292d30 100644 --- a/files/materials/terrain.shader +++ b/files/materials/terrain.shader @@ -335,7 +335,7 @@ float2 blendUV = (UV - 0.5) * (16.0 / (16.0+1.0)) + 0.5; float4 albedo = float4(0,0,0,1); - float2 layerUV = UV * 16; + float2 layerUV = float2(UV.x, 1.f-UV.y) * 16; // Reverse Y, required to get proper tangents float2 thisLayerUV; float4 normalTex; @@ -355,8 +355,6 @@ float2 blendUV = (UV - 0.5) * (16.0 / (16.0+1.0)) + 0.5; #endif thisLayerUV = layerUV; - // required to play nicely with the tangents - thisLayerUV.y *= -1; #if @shPropertyBool(use_parallax_@shIterator) thisLayerUV += TSeyeDir.xy * ( normalTex.a * PARALLAX_SCALE + PARALLAX_BIAS ); #endif From 18c002a21d9cb87ac33db74108ae4d1e91e3e52f Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 19 Dec 2013 01:31:42 +0100 Subject: [PATCH 433/434] Fix an awful typo. --- components/terrain/material.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index b421de5a28..511d45f412 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -220,9 +220,8 @@ namespace Terrain ++neededTextureUnits; // layer texture // Check if this layer has a normal map - if (mNormalMapping && !mLayerList[layerOffset].mNormalMap.empty()) + if (mNormalMapping && !mLayerList[layerIndex].mNormalMap.empty() && !renderCompositeMap) ++neededTextureUnits; // normal map - if (neededTextureUnits <= remainingTextureUnits) { // We can fit another! From 5fd98d7c3ad9edf31868953287881ce2bc3d8c7a Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 19 Dec 2013 01:41:36 +0100 Subject: [PATCH 434/434] Add an assertion --- components/terrain/material.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index 511d45f412..5dcdb7fed8 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -334,6 +334,8 @@ namespace Terrain // Make sure the pass index is fed to the permutation handler, because blendmap components may be different p->mShaderProperties.setProperty ("pass_index", sh::makeProperty(new sh::IntValue(layerOffset))); + assert ((int)p->mTexUnits.size() == OGRE_MAX_TEXTURE_LAYERS - remainingTextureUnits); + layerOffset += numLayersInThisPass; } }