1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-01-19 21:23:52 +00:00

Merge branch 'next' of https://github.com/zinnschlag/openmw into graphics

This commit is contained in:
scrawl 2013-02-09 16:40:10 +01:00
commit d47090b312
81 changed files with 1895 additions and 673 deletions

View file

@ -209,7 +209,10 @@ void loadCell(ESM::Cell &cell, ESM::ESMReader &esm, Arguments& info)
bool save = (info.mode == "clone"); bool save = (info.mode == "clone");
// Skip back to the beginning of the reference list // Skip back to the beginning of the reference list
cell.restore(esm); // FIXME: Changes to the references backend required to support multiple plugins have
// almost certainly broken this following line. I'll leave it as is for now, so that
// the compiler does not complain.
cell.restore(esm, 0);
// Loop through all the references // Loop through all the references
ESM::CellRef ref; ESM::CellRef ref;

View file

@ -1,17 +1,9 @@
set(LAUNCHER set(LAUNCHER
datafilespage.cpp
graphicspage.cpp graphicspage.cpp
main.cpp main.cpp
maindialog.cpp maindialog.cpp
playpage.cpp playpage.cpp
datafilespage.cpp
model/datafilesmodel.cpp
model/modelitem.cpp
model/esm/esmfile.cpp
utils/filedialog.cpp
utils/naturalsort.cpp
utils/lineedit.cpp
utils/profilescombobox.cpp utils/profilescombobox.cpp
utils/textinputdialog.cpp utils/textinputdialog.cpp
@ -19,36 +11,20 @@ set(LAUNCHER
) )
set(LAUNCHER_HEADER set(LAUNCHER_HEADER
datafilespage.hpp
graphicspage.hpp graphicspage.hpp
maindialog.hpp maindialog.hpp
playpage.hpp playpage.hpp
datafilespage.hpp
model/datafilesmodel.hpp
model/modelitem.hpp
model/esm/esmfile.hpp
utils/lineedit.hpp
utils/filedialog.hpp
utils/naturalsort.hpp
utils/profilescombobox.hpp utils/profilescombobox.hpp
utils/textinputdialog.hpp utils/textinputdialog.hpp
) )
# Headers that must be pre-processed # Headers that must be pre-processed
set(LAUNCHER_HEADER_MOC set(LAUNCHER_HEADER_MOC
datafilespage.hpp
graphicspage.hpp graphicspage.hpp
maindialog.hpp maindialog.hpp
playpage.hpp playpage.hpp
datafilespage.hpp
model/datafilesmodel.hpp
model/modelitem.hpp
model/esm/esmfile.hpp
utils/lineedit.hpp
utils/filedialog.hpp
utils/profilescombobox.hpp utils/profilescombobox.hpp
utils/textinputdialog.hpp utils/textinputdialog.hpp
) )

View file

@ -2,14 +2,17 @@
#include <components/esm/esmreader.hpp> #include <components/esm/esmreader.hpp>
#include <components/files/configurationmanager.hpp> #include <components/files/configurationmanager.hpp>
#include <components/fileorderlist/datafileslist.hpp>
#include <components/fileorderlist/utils/lineedit.hpp>
#include <components/fileorderlist/utils/naturalsort.hpp>
#include <components/fileorderlist/utils/filedialog.hpp>
#include "model/datafilesmodel.hpp" ////#include "model/datafilesmodel.hpp"
#include "model/esm/esmfile.hpp" ////#include "model/esm/esmfile.hpp"
#include "utils/profilescombobox.hpp" #include "utils/profilescombobox.hpp"
#include "utils/filedialog.hpp" ////#include "utils/filedialog.hpp"
#include "utils/lineedit.hpp" ////#include "utils/naturalsort.hpp"
#include "utils/naturalsort.hpp"
#include "utils/textinputdialog.hpp" #include "utils/textinputdialog.hpp"
#include "datafilespage.hpp" #include "datafilespage.hpp"
@ -34,108 +37,11 @@ namespace boost
using namespace ESM; using namespace ESM;
using namespace std; using namespace std;
//sort QModelIndexList ascending
bool rowGreaterThan(const QModelIndex &index1, const QModelIndex &index2)
{
return index1.row() >= index2.row();
}
//sort QModelIndexList descending
bool rowSmallerThan(const QModelIndex &index1, const QModelIndex &index2)
{
return index1.row() <= index2.row();
}
DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, QWidget *parent) DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, QWidget *parent)
: QWidget(parent) : QWidget(parent)
, mCfgMgr(cfg) , mCfgMgr(cfg)
{ {
// Models mDataFilesList = new DataFilesList(mCfgMgr, this);
mMastersModel = new DataFilesModel(this);
mPluginsModel = new DataFilesModel(this);
mPluginsProxyModel = new QSortFilterProxyModel();
mPluginsProxyModel->setDynamicSortFilter(true);
mPluginsProxyModel->setSourceModel(mPluginsModel);
// Filter toolbar
QLabel *filterLabel = new QLabel(tr("&Filter:"), this);
LineEdit *filterLineEdit = new LineEdit(this);
filterLabel->setBuddy(filterLineEdit);
QToolBar *filterToolBar = new QToolBar(this);
filterToolBar->setMovable(false);
// Create a container widget and a layout to get the spacer to work
QWidget *filterWidget = new QWidget(this);
QHBoxLayout *filterLayout = new QHBoxLayout(filterWidget);
QSpacerItem *hSpacer1 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
filterLayout->addItem(hSpacer1);
filterLayout->addWidget(filterLabel);
filterLayout->addWidget(filterLineEdit);
filterToolBar->addWidget(filterWidget);
QCheckBox checkBox;
unsigned int height = checkBox.sizeHint().height() + 4;
mMastersTable = new QTableView(this);
mMastersTable->setModel(mMastersModel);
mMastersTable->setObjectName("MastersTable");
mMastersTable->setSelectionBehavior(QAbstractItemView::SelectRows);
mMastersTable->setSelectionMode(QAbstractItemView::SingleSelection);
mMastersTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
mMastersTable->setAlternatingRowColors(true);
mMastersTable->horizontalHeader()->setStretchLastSection(true);
mMastersTable->horizontalHeader()->hide();
// Set the row height to the size of the checkboxes
mMastersTable->verticalHeader()->setDefaultSectionSize(height);
mMastersTable->verticalHeader()->setResizeMode(QHeaderView::Fixed);
mMastersTable->verticalHeader()->hide();
mMastersTable->setColumnHidden(1, true);
mMastersTable->setColumnHidden(2, true);
mMastersTable->setColumnHidden(3, true);
mMastersTable->setColumnHidden(4, true);
mMastersTable->setColumnHidden(5, true);
mMastersTable->setColumnHidden(6, true);
mMastersTable->setColumnHidden(7, true);
mMastersTable->setColumnHidden(8, true);
mPluginsTable = new QTableView(this);
mPluginsTable->setModel(mPluginsProxyModel);
mPluginsTable->setObjectName("PluginsTable");
mPluginsTable->setContextMenuPolicy(Qt::CustomContextMenu);
mPluginsTable->setSelectionBehavior(QAbstractItemView::SelectRows);
mPluginsTable->setSelectionMode(QAbstractItemView::SingleSelection);
mPluginsTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
mPluginsTable->setAlternatingRowColors(true);
mPluginsTable->setVerticalScrollMode(QAbstractItemView::ScrollPerItem);
mPluginsTable->horizontalHeader()->setStretchLastSection(true);
mPluginsTable->horizontalHeader()->hide();
mPluginsTable->verticalHeader()->setDefaultSectionSize(height);
mPluginsTable->verticalHeader()->setResizeMode(QHeaderView::Fixed);
mPluginsTable->setColumnHidden(1, true);
mPluginsTable->setColumnHidden(2, true);
mPluginsTable->setColumnHidden(3, true);
mPluginsTable->setColumnHidden(4, true);
mPluginsTable->setColumnHidden(5, true);
mPluginsTable->setColumnHidden(6, true);
mPluginsTable->setColumnHidden(7, true);
mPluginsTable->setColumnHidden(8, true);
// Add both tables to a splitter
QSplitter *splitter = new QSplitter(this);
splitter->setOrientation(Qt::Horizontal);
splitter->addWidget(mMastersTable);
splitter->addWidget(mPluginsTable);
// Adjust the default widget widths inside the splitter
QList<int> sizeList;
sizeList << 175 << 200;
splitter->setSizes(sizeList);
// Bottom part with profile options // Bottom part with profile options
QLabel *profileLabel = new QLabel(tr("Current Profile: "), this); QLabel *profileLabel = new QLabel(tr("Current Profile: "), this);
@ -155,24 +61,14 @@ DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, QWidget *parent)
QVBoxLayout *pageLayout = new QVBoxLayout(this); QVBoxLayout *pageLayout = new QVBoxLayout(this);
pageLayout->addWidget(filterToolBar); pageLayout->addWidget(mDataFilesList);
pageLayout->addWidget(splitter);
pageLayout->addWidget(mProfileToolBar); pageLayout->addWidget(mProfileToolBar);
// Create a dialog for the new profile name input // Create a dialog for the new profile name input
mNewProfileDialog = new TextInputDialog(tr("New Profile"), tr("Profile name:"), this); mNewProfileDialog = new TextInputDialog(tr("New Profile"), tr("Profile name:"), this);
connect(mNewProfileDialog->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(updateOkButton(QString))); connect(mNewProfileDialog->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(updateOkButton(QString)));
connect(mPluginsTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(setCheckState(QModelIndex)));
connect(mMastersTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(setCheckState(QModelIndex)));
connect(mMastersModel, SIGNAL(checkedItemsChanged(QStringList,QStringList)), mPluginsModel, SLOT(slotcheckedItemsChanged(QStringList,QStringList)));
connect(filterLineEdit, SIGNAL(textChanged(QString)), this, SLOT(filterChanged(QString)));
connect(mPluginsTable, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint)));
connect(mProfilesComboBox, SIGNAL(profileRenamed(QString,QString)), this, SLOT(profileRenamed(QString,QString))); connect(mProfilesComboBox, SIGNAL(profileRenamed(QString,QString)), this, SLOT(profileRenamed(QString,QString)));
connect(mProfilesComboBox, SIGNAL(profileChanged(QString,QString)), this, SLOT(profileChanged(QString,QString))); connect(mProfilesComboBox, SIGNAL(profileChanged(QString,QString)), this, SLOT(profileChanged(QString,QString)));
@ -202,20 +98,6 @@ void DataFilesPage::createActions()
mProfileToolBar->addSeparator(); mProfileToolBar->addSeparator();
mProfileToolBar->addAction(mNewProfileAction); mProfileToolBar->addAction(mNewProfileAction);
mProfileToolBar->addAction(mDeleteProfileAction); mProfileToolBar->addAction(mDeleteProfileAction);
// Context menu actions
mCheckAction = new QAction(tr("Check selected"), this);
connect(mCheckAction, SIGNAL(triggered()), this, SLOT(check()));
mUncheckAction = new QAction(tr("Uncheck selected"), this);
connect(mUncheckAction, SIGNAL(triggered()), this, SLOT(uncheck()));
// Context menu for the plugins table
mContextMenu = new QMenu(this);
mContextMenu->addAction(mCheckAction);
mContextMenu->addAction(mUncheckAction);
} }
void DataFilesPage::setupConfig() void DataFilesPage::setupConfig()
@ -267,12 +149,8 @@ void DataFilesPage::setupConfig()
void DataFilesPage::readConfig() void DataFilesPage::readConfig()
{ {
// Don't read the config if no masters are found
if (mMastersModel->rowCount() < 1)
return;
QString profile = mProfilesComboBox->currentText(); QString profile = mProfilesComboBox->currentText();
// Make sure we have no groups open // Make sure we have no groups open
while (!mLauncherConfig->group().isEmpty()) { while (!mLauncherConfig->group().isEmpty()) {
mLauncherConfig->endGroup(); mLauncherConfig->endGroup();
@ -290,54 +168,11 @@ void DataFilesPage::readConfig()
foreach (const QString &key, childKeys) { foreach (const QString &key, childKeys) {
const QString keyValue = mLauncherConfig->value(key).toString(); const QString keyValue = mLauncherConfig->value(key).toString();
if (key.startsWith("Plugin")) { mDataFilesList->setCheckState(keyValue, Qt::Checked);
//QStringList checked = mPluginsModel->checkedItems();
EsmFile *file = mPluginsModel->findItem(keyValue);
QModelIndex index = mPluginsModel->indexFromItem(file);
mPluginsModel->setCheckState(index, Qt::Checked);
// Move the row to the top of te view
//mPluginsModel->moveRow(index.row(), checked.count());
plugins << keyValue;
}
if (key.startsWith("Master")) {
EsmFile *file = mMastersModel->findItem(keyValue);
mMastersModel->setCheckState(mMastersModel->indexFromItem(file), Qt::Checked);
}
} }
qDebug() << plugins; qDebug() << plugins;
// // Set the checked item positions
// const QStringList checked = mPluginsModel->checkedItems();
// for (int i = 0; i < plugins.size(); ++i) {
// EsmFile *file = mPluginsModel->findItem(plugins.at(i));
// QModelIndex index = mPluginsModel->indexFromItem(file);
// mPluginsModel->moveRow(index.row(), i);
// qDebug() << "Moving: " << plugins.at(i) << " from: " << index.row() << " to: " << i << " count: " << checked.count();
// }
// Iterate over the plugins to set their checkstate and position
// for (int i = 0; i < plugins.size(); ++i) {
// const QString plugin = plugins.at(i);
// const QList<QStandardItem *> pluginList = mPluginsModel->findItems(plugin);
// if (!pluginList.isEmpty())
// {
// foreach (const QStandardItem *currentPlugin, pluginList) {
// mPluginsModel->setData(currentPlugin->index(), Qt::Checked, Qt::CheckStateRole);
// // Move the plugin to the position specified in the config file
// mPluginsModel->insertRow(i, mPluginsModel->takeRow(currentPlugin->row()));
// }
// }
// }
} }
bool DataFilesPage::showDataFilesWarning() bool DataFilesPage::showDataFilesWarning()
@ -386,77 +221,51 @@ bool DataFilesPage::setupDataFiles()
// We use the Configuration Manager to retrieve the configuration values // We use the Configuration Manager to retrieve the configuration values
boost::program_options::variables_map variables; boost::program_options::variables_map variables;
boost::program_options::options_description desc; boost::program_options::options_description desc;
desc.add_options() desc.add_options()
("data", boost::program_options::value<Files::PathContainer>()->default_value(Files::PathContainer(), "data")->multitoken()) ("data", boost::program_options::value<Files::PathContainer>()->default_value(Files::PathContainer(), "data")->multitoken())
("data-local", boost::program_options::value<std::string>()->default_value("")) ("data-local", boost::program_options::value<std::string>()->default_value(""))
("fs-strict", boost::program_options::value<bool>()->implicit_value(true)->default_value(false)) ("fs-strict", boost::program_options::value<bool>()->implicit_value(true)->default_value(false))
("encoding", boost::program_options::value<std::string>()->default_value("win1252")); ("encoding", boost::program_options::value<std::string>()->default_value("win1252"));
boost::program_options::notify(variables); boost::program_options::notify(variables);
mCfgMgr.readConfiguration(variables, desc); mCfgMgr.readConfiguration(variables, desc);
if (variables["data"].empty()) { if (variables["data"].empty()) {
if (!showDataFilesWarning()) if (!showDataFilesWarning())
return false; return false;
} else { } else {
mDataDirs = Files::PathContainer(variables["data"].as<Files::PathContainer>()); mDataDirs = Files::PathContainer(variables["data"].as<Files::PathContainer>());
} }
std::string local = variables["data-local"].as<std::string>(); std::string local = variables["data-local"].as<std::string>();
if (!local.empty()) { if (!local.empty()) {
mDataLocal.push_back(Files::PathContainer::value_type(local)); mDataLocal.push_back(Files::PathContainer::value_type(local));
} }
mCfgMgr.processPaths(mDataDirs); mCfgMgr.processPaths(mDataDirs);
mCfgMgr.processPaths(mDataLocal); mCfgMgr.processPaths(mDataLocal);
// Second chance to display the warning, the data= entries are invalid // Second chance to display the warning, the data= entries are invalid
while (mDataDirs.empty()) { while (mDataDirs.empty()) {
if (!showDataFilesWarning()) if (!showDataFilesWarning())
return false; return false;
} }
// Set the charset for reading the esm/esp files // Set the charset for reading the esm/esp files
QString encoding = QString::fromStdString(variables["encoding"].as<std::string>()); QString encoding = QString::fromStdString(variables["encoding"].as<std::string>());
if (!encoding.isEmpty() && encoding != QLatin1String("win1252")) {
mMastersModel->setEncoding(encoding); Files::PathContainer paths;
mPluginsModel->setEncoding(encoding); paths.insert(paths.end(), mDataDirs.begin(), mDataDirs.end());
} paths.insert(paths.end(), mDataLocal.begin(), mDataLocal.end());
mDataFilesList->setupDataFiles(paths, encoding);
// Add the paths to the respective models
for (Files::PathContainer::iterator it = mDataDirs.begin(); it != mDataDirs.end(); ++it) {
QString path = QString::fromStdString(it->string());
path.remove(QChar('\"'));
mMastersModel->addMasters(path);
mPluginsModel->addPlugins(path);
}
// Same for the data-local paths
for (Files::PathContainer::iterator it = mDataLocal.begin(); it != mDataLocal.end(); ++it) {
QString path = QString::fromStdString(it->string());
path.remove(QChar('\"'));
mMastersModel->addMasters(path);
mPluginsModel->addPlugins(path);
}
mMastersModel->sort(0);
mPluginsModel->sort(0);
// mMastersTable->sortByColumn(3, Qt::AscendingOrder);
// mPluginsTable->sortByColumn(3, Qt::AscendingOrder);
readConfig(); readConfig();
return true; return true;
} }
void DataFilesPage::writeConfig(QString profile) void DataFilesPage::writeConfig(QString profile)
{ {
// Don't overwrite the config if no masters are found
if (mMastersModel->rowCount() < 1)
return;
QString pathStr = QString::fromStdString(mCfgMgr.getUserPath().string()); QString pathStr = QString::fromStdString(mCfgMgr.getUserPath().string());
QDir userPath(pathStr); QDir userPath(pathStr);
@ -579,24 +388,19 @@ void DataFilesPage::writeConfig(QString profile)
mLauncherConfig->remove(""); // Clear the subgroup mLauncherConfig->remove(""); // Clear the subgroup
// Now write the masters to the configs // Now write the masters to the configs
const QStringList masters = mMastersModel->checkedItems(); const QStringList checkedFiles = mDataFilesList->checkedFiles();
for(int i=0; i < checkedFiles.size(); i++)
// We don't use foreach because we need i {
for (int i = 0; i < masters.size(); ++i) { if (checkedFiles.at(i).lastIndexOf("esm") != -1)
const QString currentMaster = masters.at(i); {
mLauncherConfig->setValue(QString("Master%0").arg(i), checkedFiles.at(i));
mLauncherConfig->setValue(QString("Master%0").arg(i), currentMaster); gameConfig << "master=" << checkedFiles.at(i) << endl;
gameConfig << "master=" << currentMaster << endl; }
else
} {
mLauncherConfig->setValue(QString("Plugin%1").arg(i), checkedFiles.at(i));
// And finally write all checked plugins gameConfig << "plugin=" << checkedFiles.at(i) << endl;
const QStringList plugins = mPluginsModel->checkedItems(); }
for (int i = 0; i < plugins.size(); ++i) {
const QString currentPlugin = plugins.at(i);
mLauncherConfig->setValue(QString("Plugin%1").arg(i), currentPlugin);
gameConfig << "plugin=" << currentPlugin << endl;
} }
file.close(); file.close();
@ -670,93 +474,6 @@ void DataFilesPage::deleteProfile()
} }
} }
void DataFilesPage::check()
{
// Check the current selection
if (!mPluginsTable->selectionModel()->hasSelection()) {
return;
}
QModelIndexList indexes = mPluginsTable->selectionModel()->selectedIndexes();
//sort selection ascending because selectedIndexes returns an unsorted list
//qSort(indexes.begin(), indexes.end(), rowSmallerThan);
foreach (const QModelIndex &index, indexes) {
if (!index.isValid())
return;
mPluginsModel->setCheckState(index, Qt::Checked);
}
}
void DataFilesPage::uncheck()
{
// uncheck the current selection
if (!mPluginsTable->selectionModel()->hasSelection()) {
return;
}
QModelIndexList indexes = mPluginsTable->selectionModel()->selectedIndexes();
//sort selection ascending because selectedIndexes returns an unsorted list
//qSort(indexes.begin(), indexes.end(), rowSmallerThan);
foreach (const QModelIndex &index, indexes) {
if (!index.isValid())
return;
mPluginsModel->setCheckState(index, Qt::Unchecked);
}
}
void DataFilesPage::refresh()
{
mPluginsModel->sort(0);
// Refresh the plugins table
mPluginsTable->scrollToTop();
writeConfig();
readConfig();
}
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(index);
(mPluginsModel->checkState(sourceIndex) == Qt::Checked)
? mPluginsModel->setCheckState(sourceIndex, Qt::Unchecked)
: mPluginsModel->setCheckState(sourceIndex, Qt::Checked);
}
if (object->objectName() == QLatin1String("MastersTable")) {
(mMastersModel->checkState(index) == Qt::Checked)
? mMastersModel->setCheckState(index, Qt::Unchecked)
: mMastersModel->setCheckState(index, Qt::Checked);
}
return;
}
void DataFilesPage::filterChanged(const QString filter)
{
QRegExp regExp(filter, Qt::CaseInsensitive, QRegExp::FixedString);
mPluginsProxyModel->setFilterRegExp(regExp);
}
void DataFilesPage::profileChanged(const QString &previous, const QString &current) void DataFilesPage::profileChanged(const QString &previous, const QString &current)
{ {
qDebug() << "Profile is changed from: " << previous << " to " << current; qDebug() << "Profile is changed from: " << previous << " to " << current;
@ -780,8 +497,7 @@ void DataFilesPage::profileChanged(const QString &previous, const QString &curre
return; return;
} }
mMastersModel->uncheckAll(); mDataFilesList->uncheckAll();
mPluginsModel->uncheckAll();
readConfig(); readConfig();
} }
@ -810,35 +526,8 @@ void DataFilesPage::profileRenamed(const QString &previous, const QString &curre
// Remove the profile from the combobox // Remove the profile from the combobox
mProfilesComboBox->removeItem(mProfilesComboBox->findText(previous)); mProfilesComboBox->removeItem(mProfilesComboBox->findText(previous));
mMastersModel->uncheckAll(); mDataFilesList->uncheckAll();
mPluginsModel->uncheckAll(); ////mMastersModel->uncheckAll();
////mPluginsModel->uncheckAll();
readConfig(); readConfig();
} }
void DataFilesPage::showContextMenu(const QPoint &point)
{
// Make sure there are plugins in the view
if (!mPluginsTable->selectionModel()->hasSelection()) {
return;
}
QPoint globalPos = mPluginsTable->mapToGlobal(point);
QModelIndexList indexes = mPluginsTable->selectionModel()->selectedIndexes();
// Show the check/uncheck actions depending on the state of the selected items
mUncheckAction->setEnabled(false);
mCheckAction->setEnabled(false);
foreach (const QModelIndex &index, indexes) {
if (!index.isValid())
return;
(mPluginsModel->checkState(index) == Qt::Checked)
? mUncheckAction->setEnabled(true)
: mCheckAction->setEnabled(true);
}
// Show menu
mContextMenu->exec(globalPos);
}

View file

@ -17,6 +17,7 @@ class ProfilesComboBox;
class DataFilesModel; class DataFilesModel;
class TextInputDialog; class TextInputDialog;
class DataFilesList;
namespace Files { struct ConfigurationManager; } namespace Files { struct ConfigurationManager; }
@ -34,10 +35,6 @@ public:
bool setupDataFiles(); bool setupDataFiles();
public slots: public slots:
void setCheckState(QModelIndex index);
void filterChanged(const QString filter);
void showContextMenu(const QPoint &point);
void profileChanged(const QString &previous, const QString &current); void profileChanged(const QString &previous, const QString &current);
void profileRenamed(const QString &previous, const QString &current); void profileRenamed(const QString &previous, const QString &current);
void updateOkButton(const QString &text); void updateOkButton(const QString &text);
@ -49,21 +46,11 @@ public slots:
// void moveDown(); // void moveDown();
// void moveTop(); // void moveTop();
// void moveBottom(); // void moveBottom();
void check();
void uncheck();
void refresh();
private: private:
DataFilesModel *mMastersModel; DataFilesList *mDataFilesList;
DataFilesModel *mPluginsModel;
QSortFilterProxyModel *mPluginsProxyModel;
QTableView *mMastersTable;
QTableView *mPluginsTable;
QToolBar *mProfileToolBar; QToolBar *mProfileToolBar;
QMenu *mContextMenu;
QAction *mNewProfileAction; QAction *mNewProfileAction;
QAction *mDeleteProfileAction; QAction *mDeleteProfileAction;
@ -72,8 +59,6 @@ private:
// QAction *mMoveDownAction; // QAction *mMoveDownAction;
// QAction *mMoveTopAction; // QAction *mMoveTopAction;
// QAction *mMoveBottomAction; // QAction *mMoveBottomAction;
QAction *mCheckAction;
QAction *mUncheckAction;
Files::ConfigurationManager &mCfgMgr; Files::ConfigurationManager &mCfgMgr;
Files::PathContainer mDataDirs; Files::PathContainer mDataDirs;

View file

@ -8,8 +8,7 @@
#include <components/files/configurationmanager.hpp> #include <components/files/configurationmanager.hpp>
#include <components/files/ogreplugin.hpp> #include <components/files/ogreplugin.hpp>
#include <components/settings/settings.hpp> #include <components/settings/settings.hpp>
#include <components/fileorderlist/utils/naturalsort.hpp>
#include "utils/naturalsort.hpp"
#include "graphicspage.hpp" #include "graphicspage.hpp"

View file

@ -5,7 +5,7 @@
#include <QVBoxLayout> #include <QVBoxLayout>
#include <QValidator> #include <QValidator>
#include "lineedit.hpp" #include <components/fileorderlist/utils/lineedit.hpp>
#include "textinputdialog.hpp" #include "textinputdialog.hpp"

View file

@ -777,6 +777,39 @@ void MwIniImporter::insertMultistrmap(multistrmap &cfg, std::string key, std::st
cfg[key].push_back(value); cfg[key].push_back(value);
} }
void MwIniImporter::importArchives(multistrmap &cfg, multistrmap &ini) {
std::vector<std::string> archives;
std::string baseArchive("Archives:Archive ");
std::string archive;
// Search archives listed in ini file
multistrmap::iterator it = ini.begin();
for(int i=0; it != ini.end(); i++) {
archive = baseArchive;
archive.append(this->numberToString(i));
it = ini.find(archive);
if(it == ini.end()) {
break;
}
for(std::vector<std::string>::iterator entry = it->second.begin(); entry!=it->second.end(); ++entry) {
archives.push_back(*entry);
}
}
cfg.erase("fallback-archive");
cfg.insert( std::make_pair<std::string, std::vector<std::string> > ("fallback-archive", std::vector<std::string>()));
// Add Morrowind.bsa by default, since Vanilla loads this archive even if it
// does not appears in the ini file
cfg["fallback-archive"].push_back("Morrowind.bsa");
for(std::vector<std::string>::iterator it=archives.begin(); it!=archives.end(); ++it) {
cfg["fallback-archive"].push_back(*it);
}
}
void MwIniImporter::importGameFiles(multistrmap &cfg, multistrmap &ini) { void MwIniImporter::importGameFiles(multistrmap &cfg, multistrmap &ini) {
std::vector<std::string> esmFiles; std::vector<std::string> esmFiles;
std::vector<std::string> espFiles; std::vector<std::string> espFiles;
@ -794,7 +827,7 @@ void MwIniImporter::importGameFiles(multistrmap &cfg, multistrmap &ini) {
} }
for(std::vector<std::string>::iterator entry = it->second.begin(); entry!=it->second.end(); ++entry) { for(std::vector<std::string>::iterator entry = it->second.begin(); entry!=it->second.end(); ++entry) {
std::string filetype(entry->substr(entry->length()-4, 3)); std::string filetype(entry->substr(entry->length()-3));
Misc::StringUtils::toLower(filetype); Misc::StringUtils::toLower(filetype);
if(filetype.compare("esm") == 0) { if(filetype.compare("esm") == 0) {

View file

@ -23,6 +23,7 @@ class MwIniImporter {
void merge(multistrmap &cfg, multistrmap &ini); void merge(multistrmap &cfg, multistrmap &ini);
void mergeFallback(multistrmap &cfg, multistrmap &ini); void mergeFallback(multistrmap &cfg, multistrmap &ini);
void importGameFiles(multistrmap &cfg, multistrmap &ini); void importGameFiles(multistrmap &cfg, multistrmap &ini);
void importArchives(multistrmap &cfg, multistrmap &ini);
void writeToFile(boost::iostreams::stream<boost::iostreams::file_sink> &out, multistrmap &cfg); void writeToFile(boost::iostreams::stream<boost::iostreams::file_sink> &out, multistrmap &cfg);
private: private:

View file

@ -18,6 +18,7 @@ int main(int argc, char *argv[]) {
("cfg,c", bpo::value<std::string>(), "openmw.cfg file") ("cfg,c", bpo::value<std::string>(), "openmw.cfg file")
("output,o", bpo::value<std::string>()->default_value(""), "openmw.cfg file") ("output,o", bpo::value<std::string>()->default_value(""), "openmw.cfg file")
("game-files,g", "import esm and esp files") ("game-files,g", "import esm and esp files")
("no-archives,A", "disable bsa archives import")
("encoding,e", bpo::value<std::string>()-> default_value("win1252"), ("encoding,e", bpo::value<std::string>()-> default_value("win1252"),
"Character encoding used in OpenMW game messages:\n" "Character encoding used in OpenMW game messages:\n"
"\n\twin1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages\n" "\n\twin1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages\n"
@ -76,6 +77,10 @@ int main(int argc, char *argv[]) {
importer.importGameFiles(cfg, ini); importer.importGameFiles(cfg, ini);
} }
if(!vm.count("no-archives")) {
importer.importArchives(cfg, ini);
}
std::cout << "write to: " << outputFile << std::endl; std::cout << "write to: " << outputFile << std::endl;
boost::iostreams::stream<boost::iostreams::file_sink> file(outputFile); boost::iostreams::stream<boost::iostreams::file_sink> file(outputFile);
importer.writeToFile(file, cfg); importer.writeToFile(file, cfg);

View file

@ -21,6 +21,7 @@ opencs_units (model/world
idtable idtableproxymodel idtable idtableproxymodel
) )
opencs_units_noqt (model/world opencs_units_noqt (model/world
universalid data record idcollection commands columnbase universalid data record idcollection commands columnbase
) )
@ -40,9 +41,10 @@ opencs_units_noqt (model/tools
opencs_units (view/doc opencs_units (view/doc
viewmanager view operations operation subview viewmanager view operations operation subview startup opendialog
) )
opencs_units_noqt (view/doc opencs_units_noqt (view/doc
subviewfactory subviewfactory
) )

View file

@ -11,39 +11,52 @@
CS::Editor::Editor() : mViewManager (mDocumentManager), mNewDocumentIndex (0) CS::Editor::Editor() : mViewManager (mDocumentManager), mNewDocumentIndex (0)
{ {
connect (&mViewManager, SIGNAL (newDocumentRequest ()), this, SLOT (createDocument ())); connect (&mViewManager, SIGNAL (newDocumentRequest ()), this, SLOT (createDocument ()));
connect (&mViewManager, SIGNAL (loadDocumentRequest ()), this, SLOT (loadDocument ()));
connect (&mStartup, SIGNAL (createDocument()), this, SLOT (createDocument ()));
connect (&mStartup, SIGNAL (loadDocument()), this, SLOT (loadDocument ()));
connect (&mOpenDialog, SIGNAL(accepted()), this, SLOT(openFiles()));
} }
void CS::Editor::createDocument() void CS::Editor::createDocument()
{ {
mStartup.hide();
/// \todo open the ESX picker instead
std::ostringstream stream; std::ostringstream stream;
stream << "NewDocument" << (++mNewDocumentIndex); stream << "NewDocument" << (++mNewDocumentIndex);
CSMDoc::Document *document = mDocumentManager.addDocument (stream.str()); std::vector<boost::filesystem::path> files;
files.push_back (stream.str());
static const char *sGlobals[] = CSMDoc::Document *document = mDocumentManager.addDocument (files, true);
{
"Day", "DaysPassed", "GameHour", "Month", "PCRace", "PCVampire", "PCWerewolf", "PCYear", 0
};
for (int i=0; sGlobals[i]; ++i) mViewManager.addView (document);
{ }
ESM::Global record;
record.mId = sGlobals[i];
record.mValue = i==0 ? 1 : 0;
record.mType = ESM::VT_Float;
document->getData().getGlobals().add (record);
}
document->getData().merge(); /// \todo remove once proper ESX loading is implemented void CS::Editor::loadDocument()
{
mStartup.hide();
mOpenDialog.show();
mOpenDialog.raise();
mOpenDialog.activateWindow();
}
void CS::Editor::openFiles()
{
std::vector<boost::filesystem::path> paths;
mOpenDialog.getFileList(paths);
CSMDoc::Document *document = mDocumentManager.addDocument(paths, false);
mViewManager.addView (document); mViewManager.addView (document);
} }
int CS::Editor::run() int CS::Editor::run()
{ {
/// \todo Instead of creating an empty document, open a small welcome dialogue window with buttons for new/load/recent projects mStartup.show();
createDocument();
return QApplication::exec(); return QApplication::exec();
} }

View file

@ -4,7 +4,10 @@
#include <QObject> #include <QObject>
#include "model/doc/documentmanager.hpp" #include "model/doc/documentmanager.hpp"
#include "view/doc/viewmanager.hpp" #include "view/doc/viewmanager.hpp"
#include "view/doc/startup.hpp"
#include "view/doc/opendialog.hpp"
namespace CS namespace CS
{ {
@ -16,6 +19,8 @@ namespace CS
CSMDoc::DocumentManager mDocumentManager; CSMDoc::DocumentManager mDocumentManager;
CSVDoc::ViewManager mViewManager; CSVDoc::ViewManager mViewManager;
CSVDoc::StartupDialogue mStartup;
OpenDialog mOpenDialog;
// not implemented // not implemented
Editor (const Editor&); Editor (const Editor&);
@ -28,9 +33,12 @@ namespace CS
int run(); int run();
///< \return error status ///< \return error status
public slots: private slots:
void createDocument(); void createDocument();
void loadDocument();
void openFiles();
}; };
} }

View file

@ -1,10 +1,66 @@
#include "document.hpp" #include "document.hpp"
CSMDoc::Document::Document (const std::string& name) #include <cassert>
void CSMDoc::Document::load (const std::vector<boost::filesystem::path>::const_iterator& begin,
const std::vector<boost::filesystem::path>::const_iterator& end, bool lastAsModified)
{
assert (begin!=end);
std::vector<boost::filesystem::path>::const_iterator end2 (end);
if (lastAsModified)
--end2;
for (std::vector<boost::filesystem::path>::const_iterator iter (begin); iter!=end2; ++iter)
getData().loadFile (*iter, true);
if (lastAsModified)
getData().loadFile (*end2, false);
}
void CSMDoc::Document::createBase()
{
static const char *sGlobals[] =
{
"Day", "DaysPassed", "GameHour", "Month", "PCRace", "PCVampire", "PCWerewolf", "PCYear", 0
};
for (int i=0; sGlobals[i]; ++i)
{
ESM::Global record;
record.mId = sGlobals[i];
record.mValue = i==0 ? 1 : 0;
record.mType = ESM::VT_Float;
getData().getGlobals().add (record);
}
}
CSMDoc::Document::Document (const std::vector<boost::filesystem::path>& files, bool new_)
: mTools (mData) : mTools (mData)
{ {
mName = name; ///< \todo replace with ESX list if (files.empty())
throw std::runtime_error ("Empty content file sequence");
/// \todo adjust last file name:
/// \li make sure it is located in the data-local directory (adjust path if necessary)
/// \li make sure the extension matches the new scheme (change it if necesarry)
mName = files.back().filename().string();
if (files.size()>1 || !new_)
{
std::vector<boost::filesystem::path>::const_iterator end = files.end();
if (new_)
--end;
load (files.begin(), end, !new_);
}
if (new_ && files.size()==1)
createBase();
connect (&mUndoStack, SIGNAL (cleanChanged (bool)), this, SLOT (modificationStateChanged (bool))); connect (&mUndoStack, SIGNAL (cleanChanged (bool)), this, SLOT (modificationStateChanged (bool)));
@ -86,10 +142,10 @@ void CSMDoc::Document::saving()
if (mSaveCount>15) if (mSaveCount>15)
{ {
mSaveCount = 0; mSaveCount = 0;
mSaveTimer.stop(); mSaveTimer.stop();
mUndoStack.setClean(); mUndoStack.setClean();
emit stateChanged (getState(), this); emit stateChanged (getState(), this);
} }
} }

View file

@ -3,6 +3,8 @@
#include <string> #include <string>
#include <boost/filesystem/path.hpp>
#include <QUndoStack> #include <QUndoStack>
#include <QObject> #include <QObject>
#include <QTimer> #include <QTimer>
@ -38,10 +40,15 @@ namespace CSMDoc
Document (const Document&); Document (const Document&);
Document& operator= (const Document&); Document& operator= (const Document&);
void load (const std::vector<boost::filesystem::path>::const_iterator& begin,
const std::vector<boost::filesystem::path>::const_iterator& end, bool lastAsModified);
///< \param lastAsModified Store the last file in Modified instead of merging it into Base.
void createBase();
public: public:
Document (const std::string& name); Document (const std::vector<boost::filesystem::path>& files, bool new_);
///< \todo replace name with ESX list
QUndoStack& getUndoStack(); QUndoStack& getUndoStack();

View file

@ -14,9 +14,10 @@ CSMDoc::DocumentManager::~DocumentManager()
delete *iter; delete *iter;
} }
CSMDoc::Document *CSMDoc::DocumentManager::addDocument (const std::string& name) CSMDoc::Document *CSMDoc::DocumentManager::addDocument (const std::vector<boost::filesystem::path>& files,
bool new_)
{ {
Document *document = new Document (name); Document *document = new Document (files, new_);
mDocuments.push_back (document); mDocuments.push_back (document);

View file

@ -4,6 +4,8 @@
#include <vector> #include <vector>
#include <string> #include <string>
#include <boost/filesystem/path.hpp>
namespace CSMDoc namespace CSMDoc
{ {
class Document; class Document;
@ -21,8 +23,11 @@ namespace CSMDoc
~DocumentManager(); ~DocumentManager();
Document *addDocument (const std::string& name); Document *addDocument (const std::vector<boost::filesystem::path>& files, bool new_);
///< The ownership of the returned document is not transferred to the caller. ///< 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
/// appropriate way.
bool removeDocument (Document *document); bool removeDocument (Document *document);
///< \return last document removed? ///< \return last document removed?

View file

@ -28,7 +28,8 @@ namespace CSMWorld
{ {
Display_String, Display_String,
Display_Integer, Display_Integer,
Display_Float Display_Float,
Display_Var
}; };
std::string mTitle; std::string mTitle;

View file

@ -17,9 +17,9 @@ namespace CSMWorld
virtual void set (Record<ESXRecordT>& record, const QVariant& data) virtual void set (Record<ESXRecordT>& record, const QVariant& data)
{ {
ESXRecordT base = record.getBase(); ESXRecordT record2 = record.get();
base.mValue = data.toFloat(); record2.mValue = data.toFloat();
record.setModified (base); record.setModified (record2);
} }
virtual bool isEditable() const virtual bool isEditable() const
@ -91,6 +91,68 @@ namespace CSMWorld
return false; return false;
} }
}; };
template<typename ESXRecordT>
struct VarTypeColumn : public Column<ESXRecordT>
{
VarTypeColumn() : Column<ESXRecordT> ("Type", ColumnBase::Display_Integer) {}
virtual QVariant get (const Record<ESXRecordT>& record) const
{
return static_cast<int> (record.get().mType);
}
virtual void set (Record<ESXRecordT>& record, const QVariant& data)
{
ESXRecordT record2 = record.get();
record2.mType = static_cast<ESM::VarType> (data.toInt());
record.setModified (record2);
}
virtual bool isEditable() const
{
return true;
}
};
template<typename ESXRecordT>
struct VarValueColumn : public Column<ESXRecordT>
{
VarValueColumn() : Column<ESXRecordT> ("Value", ColumnBase::Display_Var) {}
virtual QVariant get (const Record<ESXRecordT>& record) const
{
switch (record.get().mType)
{
case ESM::VT_String: return record.get().mStr.c_str(); break;
case ESM::VT_Int: return record.get().mI; break;
case ESM::VT_Float: return record.get().mF; break;
default: return QVariant();
}
}
virtual void set (Record<ESXRecordT>& record, const QVariant& data)
{
ESXRecordT record2 = record.get();
switch (record2.mType)
{
case ESM::VT_String: record2.mStr = data.toString().toUtf8().constData(); break;
case ESM::VT_Int: record2.mI = data.toInt(); break;
case ESM::VT_Float: record2.mF = data.toFloat(); break;
default: break;
}
record.setModified (record2);
}
virtual bool isEditable() const
{
return true;
}
};
} }
#endif #endif

View file

@ -5,6 +5,7 @@
#include <QAbstractTableModel> #include <QAbstractTableModel>
#include <components/esm/esmreader.hpp>
#include <components/esm/loadglob.hpp> #include <components/esm/loadglob.hpp>
#include "idtable.hpp" #include "idtable.hpp"
@ -27,7 +28,14 @@ CSMWorld::Data::Data()
mGlobals.addColumn (new FixedRecordTypeColumn<ESM::Global> (UniversalId::Type_Global)); mGlobals.addColumn (new FixedRecordTypeColumn<ESM::Global> (UniversalId::Type_Global));
mGlobals.addColumn (new FloatValueColumn<ESM::Global>); mGlobals.addColumn (new FloatValueColumn<ESM::Global>);
mGmsts.addColumn (new StringIdColumn<ESM::GameSetting>);
mGmsts.addColumn (new RecordStateColumn<ESM::GameSetting>);
mGmsts.addColumn (new FixedRecordTypeColumn<ESM::GameSetting> (UniversalId::Type_Gmst));
mGmsts.addColumn (new VarTypeColumn<ESM::GameSetting>);
mGmsts.addColumn (new VarValueColumn<ESM::GameSetting>);
addModel (new IdTable (&mGlobals), UniversalId::Type_Globals, UniversalId::Type_Global); addModel (new IdTable (&mGlobals), UniversalId::Type_Globals, UniversalId::Type_Global);
addModel (new IdTable (&mGmsts), UniversalId::Type_Gmsts, UniversalId::Type_Gmst);
} }
CSMWorld::Data::~Data() CSMWorld::Data::~Data()
@ -59,4 +67,31 @@ QAbstractTableModel *CSMWorld::Data::getTableModel (const UniversalId& id)
void CSMWorld::Data::merge() void CSMWorld::Data::merge()
{ {
mGlobals.merge(); mGlobals.merge();
}
void CSMWorld::Data::loadFile (const boost::filesystem::path& path, bool base)
{
ESM::ESMReader reader;
/// \todo set encoder
reader.open (path.string());
// 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())
{
ESM::NAME n = reader.getRecName();
reader.getRecHeader();
switch (n.val)
{
case ESM::REC_GLOB: mGlobals.load (reader, base); break;
case ESM::REC_GMST: mGmsts.load (reader, base); break;
default:
/// \todo throw an exception instead, once all records are implemented
reader.skipRecord();
}
}
} }

View file

@ -4,7 +4,10 @@
#include <map> #include <map>
#include <vector> #include <vector>
#include <boost/filesystem/path.hpp>
#include <components/esm/loadglob.hpp> #include <components/esm/loadglob.hpp>
#include <components/esm/loadgmst.hpp>
#include "idcollection.hpp" #include "idcollection.hpp"
#include "universalid.hpp" #include "universalid.hpp"
@ -16,6 +19,7 @@ namespace CSMWorld
class Data class Data
{ {
IdCollection<ESM::Global> mGlobals; IdCollection<ESM::Global> mGlobals;
IdCollection<ESM::GameSetting> mGmsts;
std::vector<QAbstractTableModel *> mModels; std::vector<QAbstractTableModel *> mModels;
std::map<UniversalId::Type, QAbstractTableModel *> mModelIndex; std::map<UniversalId::Type, QAbstractTableModel *> mModelIndex;
@ -44,6 +48,9 @@ namespace CSMWorld
void merge(); void merge();
///< Merge modified into base. ///< Merge modified into base.
void loadFile (const boost::filesystem::path& path, bool base);
///< Merging content of a file into base or modified.
}; };
} }

View file

@ -3,4 +3,4 @@
CSMWorld::IdCollectionBase::IdCollectionBase() {} CSMWorld::IdCollectionBase::IdCollectionBase() {}
CSMWorld::IdCollectionBase::~IdCollectionBase() {} CSMWorld::IdCollectionBase::~IdCollectionBase() {}

View file

@ -11,9 +11,12 @@
#include <QVariant> #include <QVariant>
#include "columnbase.hpp" #include <components/esm/esmreader.hpp>
#include <components/misc/stringops.hpp> #include <components/misc/stringops.hpp>
#include "columnbase.hpp"
namespace CSMWorld namespace CSMWorld
{ {
class IdCollectionBase class IdCollectionBase
@ -67,9 +70,11 @@ namespace CSMWorld
virtual std::string getId (const RecordBase& record) const = 0; virtual std::string getId (const RecordBase& record) const = 0;
///< Return ID for \a record. ///< Return ID for \a record.
/// ///
/// \attention Throw san exception, if the type of \a record does not match. /// \attention Throws an exception, if the type of \a record does not match.
virtual const RecordBase& getRecord (const std::string& id) const = 0; virtual const RecordBase& getRecord (const std::string& id) const = 0;
virtual void load (ESM::ESMReader& reader, bool base) = 0;
}; };
///< \brief Collection of ID-based records ///< \brief Collection of ID-based records
@ -136,6 +141,8 @@ namespace CSMWorld
virtual const RecordBase& getRecord (const std::string& id) const; virtual const RecordBase& getRecord (const std::string& id) const;
virtual void load (ESM::ESMReader& reader, bool base);
void addColumn (Column<ESXRecordT> *column); void addColumn (Column<ESXRecordT> *column);
}; };
@ -309,6 +316,62 @@ namespace CSMWorld
return (record2.isModified() ? record2.mModified : record2.mBase).mId; return (record2.isModified() ? record2.mModified : record2.mBase).mId;
} }
template<typename ESXRecordT>
void IdCollection<ESXRecordT>::load (ESM::ESMReader& reader, bool base)
{
std::string id = reader.getHNOString ("NAME");
int index = searchId (id);
if (reader.isNextSub ("DELE"))
{
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
{
mRecords[index].mState = RecordBase::State_Deleted;
}
}
else
{
ESXRecordT record;
record.mId = id;
record.load (reader);
if (index==-1)
{
// new record
Record<ESXRecordT> record2;
record2.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly;
(base ? record2.mBase : record2.mModified) = record;
appendRecord (record2);
}
else
{
// old record
Record<ESXRecordT>& record2 = mRecords[index];
if (base)
record2.mBase = record;
else
record2.setModified (record);
}
}
}
template<typename ESXRecordT> template<typename ESXRecordT>
const RecordBase& IdCollection<ESXRecordT>::getRecord (const std::string& id) const const RecordBase& IdCollection<ESXRecordT>::getRecord (const std::string& id) const
{ {

View file

@ -18,6 +18,7 @@ namespace
{ {
{ CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, "empty" }, { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, "empty" },
{ CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Globals, "Global Variables" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Globals, "Global Variables" },
{ CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Gmsts, "Game Settings" },
{ CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0 } // end marker { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0 } // end marker
}; };
@ -25,6 +26,7 @@ namespace
static const TypeData sIdArg[] = static const TypeData sIdArg[] =
{ {
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Global, "Global Variable" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Global, "Global Variable" },
{ CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Gmst, "Game Setting" },
{ CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0 } // end marker { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0 } // end marker
}; };

View file

@ -33,12 +33,12 @@ namespace CSMWorld
enum Type enum Type
{ {
Type_None, Type_None,
Type_Globals, Type_Globals,
Type_Global, Type_Global,
Type_VerificationResults,
Type_Gmsts,
Type_Gmst
Type_VerificationResults
}; };
private: private:

View file

@ -0,0 +1,62 @@
#include <QVBoxLayout>
#include <QDialogButtonBox>
#include <components/fileorderlist/datafileslist.hpp>
#include "opendialog.hpp"
OpenDialog::OpenDialog(QWidget * parent) : QDialog(parent)
{
QVBoxLayout *layout = new QVBoxLayout(this);
mFileSelector = new DataFilesList(mCfgMgr, this);
layout->addWidget(mFileSelector);
//FIXME - same as DataFilesPage::setupDataFiles
// We use the Configuration Manager to retrieve the configuration values
boost::program_options::variables_map variables;
boost::program_options::options_description desc;
desc.add_options()
("data", boost::program_options::value<Files::PathContainer>()->default_value(Files::PathContainer(), "data")->multitoken())
("data-local", boost::program_options::value<std::string>()->default_value(""))
("fs-strict", boost::program_options::value<bool>()->implicit_value(true)->default_value(false))
("encoding", boost::program_options::value<std::string>()->default_value("win1252"));
boost::program_options::notify(variables);
mCfgMgr.readConfiguration(variables, desc);
Files::PathContainer mDataDirs, mDataLocal;
if (!variables["data"].empty()) {
mDataDirs = Files::PathContainer(variables["data"].as<Files::PathContainer>());
}
std::string local = variables["data-local"].as<std::string>();
if (!local.empty()) {
mDataLocal.push_back(Files::PathContainer::value_type(local));
}
mCfgMgr.processPaths(mDataDirs);
mCfgMgr.processPaths(mDataLocal);
// Set the charset for reading the esm/esp files
QString encoding = QString::fromStdString(variables["encoding"].as<std::string>());
Files::PathContainer dataDirs;
dataDirs.insert(dataDirs.end(), mDataDirs.begin(), mDataDirs.end());
dataDirs.insert(dataDirs.end(), mDataLocal.begin(), mDataLocal.end());
mFileSelector->setupDataFiles(dataDirs, encoding);
buttonBox = new QDialogButtonBox(QDialogButtonBox::Open | QDialogButtonBox::Cancel, Qt::Horizontal, this);
connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
layout->addWidget(buttonBox);
setLayout(layout);
setWindowTitle(tr("Open"));
}
void OpenDialog::getFileList(std::vector<boost::filesystem::path>& paths)
{
mFileSelector->selectedFiles(paths);
}

View file

@ -0,0 +1,17 @@
#include <qdialog.h>
#include <components/files/configurationmanager.hpp>
class DataFilesList;
class QDialogButtonBox;
class OpenDialog : public QDialog {
Q_OBJECT
public:
OpenDialog(QWidget * parent = 0);
void getFileList(std::vector<boost::filesystem::path>& paths);
private:
DataFilesList * mFileSelector;
QDialogButtonBox * buttonBox;
Files::ConfigurationManager mCfgMgr;
};

View file

@ -0,0 +1,20 @@
#include "startup.hpp"
#include <QPushButton>
#include <QHBoxLayout>
CSVDoc::StartupDialogue::StartupDialogue()
{
QHBoxLayout *layout = new QHBoxLayout (this);
QPushButton *createDocument = new QPushButton ("new", this);
connect (createDocument, SIGNAL (clicked()), this, SIGNAL (createDocument()));
layout->addWidget (createDocument);
QPushButton *loadDocument = new QPushButton ("load", this);
connect (loadDocument, SIGNAL (clicked()), this, SIGNAL (loadDocument()));
layout->addWidget (loadDocument);
setLayout (layout);
}

View file

@ -0,0 +1,24 @@
#ifndef CSV_DOC_STARTUP_H
#define CSV_DOC_STARTUP_H
#include <QWidget>
namespace CSVDoc
{
class StartupDialogue : public QWidget
{
Q_OBJECT
public:
StartupDialogue();
signals:
void createDocument();
void loadDocument();
};
}
#endif

View file

@ -32,6 +32,10 @@ void CSVDoc::View::setupFileMenu()
connect (new_, SIGNAL (triggered()), this, SIGNAL (newDocumentRequest())); connect (new_, SIGNAL (triggered()), this, SIGNAL (newDocumentRequest()));
file->addAction (new_); file->addAction (new_);
QAction *open = new QAction (tr ("&Open"), this);
connect (open, SIGNAL (triggered()), this, SIGNAL (loadDocumentRequest()));
file->addAction (open);
mSave = new QAction (tr ("&Save"), this); mSave = new QAction (tr ("&Save"), this);
connect (mSave, SIGNAL (triggered()), this, SLOT (save())); connect (mSave, SIGNAL (triggered()), this, SLOT (save()));
file->addAction (mSave); file->addAction (mSave);
@ -67,6 +71,10 @@ void CSVDoc::View::setupWorldMenu()
connect (globals, SIGNAL (triggered()), this, SLOT (addGlobalsSubView())); connect (globals, SIGNAL (triggered()), this, SLOT (addGlobalsSubView()));
world->addAction (globals); world->addAction (globals);
QAction *gmsts = new QAction (tr ("Game settings"), this);
connect (gmsts, SIGNAL (triggered()), this, SLOT (addGmstsSubView()));
world->addAction (gmsts);
mVerify = new QAction (tr ("&Verify"), this); mVerify = new QAction (tr ("&Verify"), this);
connect (mVerify, SIGNAL (triggered()), this, SLOT (verify())); connect (mVerify, SIGNAL (triggered()), this, SLOT (verify()));
world->addAction (mVerify); world->addAction (mVerify);
@ -213,4 +221,9 @@ void CSVDoc::View::verify()
void CSVDoc::View::addGlobalsSubView() void CSVDoc::View::addGlobalsSubView()
{ {
addSubView (CSMWorld::UniversalId::Type_Globals); addSubView (CSMWorld::UniversalId::Type_Globals);
}
void CSVDoc::View::addGmstsSubView()
{
addSubView (CSMWorld::UniversalId::Type_Gmsts);
} }

View file

@ -84,6 +84,8 @@ namespace CSVDoc
void newDocumentRequest(); void newDocumentRequest();
void loadDocumentRequest();
public slots: public slots:
void addSubView (const CSMWorld::UniversalId& id); void addSubView (const CSMWorld::UniversalId& id);
@ -97,6 +99,8 @@ namespace CSVDoc
void verify(); void verify();
void addGlobalsSubView(); void addGlobalsSubView();
void addGmstsSubView();
}; };
} }

View file

@ -57,6 +57,7 @@ CSVDoc::View *CSVDoc::ViewManager::addView (CSMDoc::Document *document)
view->show(); view->show();
connect (view, SIGNAL (newDocumentRequest ()), this, SIGNAL (newDocumentRequest())); connect (view, SIGNAL (newDocumentRequest ()), this, SIGNAL (newDocumentRequest()));
connect (view, SIGNAL (loadDocumentRequest ()), this, SIGNAL (loadDocumentRequest()));
updateIndices(); updateIndices();

View file

@ -46,6 +46,8 @@ namespace CSVDoc
void newDocumentRequest(); void newDocumentRequest();
void loadDocumentRequest();
private slots: private slots:
void documentStateChanged (int state, CSMDoc::Document *document); void documentStateChanged (int state, CSMDoc::Document *document);

View file

@ -64,6 +64,8 @@ CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, CSM
/// \todo configure widget properly (range, format?) /// \todo configure widget properly (range, format?)
layout->addWidget (widget = new QDoubleSpinBox, i, 1); layout->addWidget (widget = new QDoubleSpinBox, i, 1);
break; break;
default: break; // silence warnings for other times for now
} }
} }
else else
@ -76,6 +78,8 @@ CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, CSM
layout->addWidget (widget = new QLabel, i, 1); layout->addWidget (widget = new QLabel, i, 1);
break; break;
default: break; // silence warnings for other times for now
} }
} }

View file

@ -11,6 +11,9 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager)
manager.add (CSMWorld::UniversalId::Type_Globals, manager.add (CSMWorld::UniversalId::Type_Globals,
new CSVDoc::SubViewFactoryWithCreateFlag<TableSubView> (true)); new CSVDoc::SubViewFactoryWithCreateFlag<TableSubView> (true));
manager.add (CSMWorld::UniversalId::Type_Gmsts,
new CSVDoc::SubViewFactoryWithCreateFlag<TableSubView> (false));
manager.add (CSMWorld::UniversalId::Type_Global, manager.add (CSMWorld::UniversalId::Type_Global,
new CSVDoc::SubViewFactoryWithCreateFlag<DialogueSubView> (true)); new CSVDoc::SubViewFactoryWithCreateFlag<DialogueSubView> (true));
} }

View file

@ -41,7 +41,7 @@ add_openmw_dir (mwscript
locals scriptmanagerimp compilercontext interpretercontext cellextensions miscextensions locals scriptmanagerimp compilercontext interpretercontext cellextensions miscextensions
guiextensions soundextensions skyextensions statsextensions containerextensions guiextensions soundextensions skyextensions statsextensions containerextensions
aiextensions controlextensions extensions globalscripts ref dialogueextensions aiextensions controlextensions extensions globalscripts ref dialogueextensions
animationextensions transformationextensions consoleextensions userextensions animationextensions transformationextensions consoleextensions userextensions locals
) )
add_openmw_dir (mwsound add_openmw_dir (mwsound

View file

@ -210,18 +210,31 @@ void OMW::Engine::setCell (const std::string& cellName)
// Set master file (esm) // Set master file (esm)
// - If the given name does not have an extension, ".esm" is added automatically // - If the given name does not have an extension, ".esm" is added automatically
// - Currently OpenMW only supports one master at the same time.
void OMW::Engine::addMaster (const std::string& master) void OMW::Engine::addMaster (const std::string& master)
{ {
assert (mMaster.empty()); mMaster.push_back(master);
mMaster = master; std::string &str = mMaster.back();
// Append .esm if not already there // Append .esm if not already there
std::string::size_type sep = mMaster.find_last_of ("."); std::string::size_type sep = str.find_last_of (".");
if (sep == std::string::npos) if (sep == std::string::npos)
{ {
mMaster += ".esm"; 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";
} }
} }
@ -326,13 +339,13 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
MWGui::CursorReplace replacer; MWGui::CursorReplace replacer;
// Create the world // Create the world
mEnvironment.setWorld (new MWWorld::World (*mOgre, mFileCollections, mMaster, mEnvironment.setWorld( new MWWorld::World (*mOgre, mFileCollections, mMaster, mPlugins,
mResDir, mCfgMgr.getCachePath(), mNewGame, mEncoder, mFallbackMap, mResDir, mCfgMgr.getCachePath(), mNewGame, mEncoder, mFallbackMap,
mActivationDistanceOverride)); mActivationDistanceOverride));
//Load translation data //Load translation data
mTranslationDataStorage.setEncoder(mEncoder); mTranslationDataStorage.setEncoder(mEncoder);
mTranslationDataStorage.loadTranslationData(mFileCollections, mMaster); mTranslationDataStorage.loadTranslationData(mFileCollections, mMaster[0]);
// Create window manager - this manages all the MW-specific GUI windows // Create window manager - this manages all the MW-specific GUI windows
MWScript::registerExtensions (mExtensions); MWScript::registerExtensions (mExtensions);

View file

@ -67,7 +67,8 @@ namespace OMW
boost::filesystem::path mResDir; boost::filesystem::path mResDir;
OEngine::Render::OgreRenderer *mOgre; OEngine::Render::OgreRenderer *mOgre;
std::string mCellName; std::string mCellName;
std::string mMaster; std::vector<std::string> mMaster;
std::vector<std::string> mPlugins;
int mFpsLevel; int mFpsLevel;
bool mDebug; bool mDebug;
bool mVerboseScripts; bool mVerboseScripts;
@ -132,9 +133,12 @@ namespace OMW
/// Set master file (esm) /// Set master file (esm)
/// - If the given name does not have an extension, ".esm" is added automatically /// - If the given name does not have an extension, ".esm" is added automatically
/// - Currently OpenMW only supports one master at the same time.
void addMaster(const std::string& master); 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);
/// Enable fps counter /// Enable fps counter
void showFPS(int level); void showFPS(int level);

View file

@ -211,18 +211,21 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
master.push_back("Morrowind"); master.push_back("Morrowind");
} }
if (master.size() > 1)
{
std::cout
<< "Ignoring all but the first master file (multiple master files not yet supported)."
<< std::endl;
}
engine.addMaster(master[0]);
StringsVector plugin = variables["plugin"].as<StringsVector>(); StringsVector plugin = variables["plugin"].as<StringsVector>();
if (!plugin.empty()) // 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<std::string>::size_type i = 0; i < master.size(); i++)
{ {
std::cout << "Ignoring plugin files (plugins not yet supported)." << std::endl; engine.addMaster(master[i]);
}
for (std::vector<std::string>::size_type i = 0; i < plugin.size(); i++)
{
engine.addPlugin(plugin[i]);
} }
// startup-settings // startup-settings

View file

@ -105,7 +105,7 @@ namespace MWBase
virtual const MWWorld::ESMStore& getStore() const = 0; virtual const MWWorld::ESMStore& getStore() const = 0;
virtual ESM::ESMReader& getEsmReader() = 0; virtual std::vector<ESM::ESMReader>& getEsmReader() = 0;
virtual MWWorld::LocalScripts& getLocalScripts() = 0; virtual MWWorld::LocalScripts& getLocalScripts() = 0;

View file

@ -7,6 +7,8 @@
#include <boost/lexical_cast.hpp> #include <boost/lexical_cast.hpp>
#include <components/compiler/locals.hpp>
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/soundmanager.hpp" #include "../mwbase/soundmanager.hpp"
@ -240,6 +242,12 @@ namespace MWGui
if (it != invStore.end() && *it == item) if (it != invStore.end() && *it == item)
{ {
invStore.equip(slot, invStore.end()); invStore.equip(slot, invStore.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(script != "")
(*it).mRefData->getLocals().setVarByInt(script, "onpcequip", 0);
return; return;
} }
} }

View file

@ -144,7 +144,7 @@ namespace MWRender
std::map<uint16_t, int> indexes; std::map<uint16_t, int> indexes;
initTerrainTextures(&terrainData, cellX, cellY, initTerrainTextures(&terrainData, cellX, cellY,
x * numTextures, y * numTextures, x * numTextures, y * numTextures,
numTextures, indexes); numTextures, indexes, land->mPlugin);
if (mTerrainGroup.getTerrain(terrainX, terrainY) == NULL) if (mTerrainGroup.getTerrain(terrainX, terrainY) == NULL)
{ {
@ -213,8 +213,13 @@ namespace MWRender
void TerrainManager::initTerrainTextures(Terrain::ImportData* terrainData, void TerrainManager::initTerrainTextures(Terrain::ImportData* terrainData,
int cellX, int cellY, int cellX, int cellY,
int fromX, int fromY, int size, int fromX, int fromY, int size,
std::map<uint16_t, int>& indexes) std::map<uint16_t, int>& indexes, size_t plugin)
{ {
// FIXME: In a multiple esm configuration, we have multiple palettes. Since this code
// crosses cell boundaries, we no longer have a unique terrain palette. Instead, we need
// to adopt the following code for a dynamic palette. And this is evil - the current design
// does not work well for this task...
assert(terrainData != NULL && "Must have valid terrain data"); assert(terrainData != NULL && "Must have valid terrain data");
assert(fromX >= 0 && fromY >= 0 && assert(fromX >= 0 && fromY >= 0 &&
"Can't get a terrain texture on terrain outside the current cell"); "Can't get a terrain texture on terrain outside the current cell");
@ -227,12 +232,20 @@ namespace MWRender
// //
//If we don't sort the ltex indexes, the splatting order may differ between //If we don't sort the ltex indexes, the splatting order may differ between
//cells which may lead to inconsistent results when shading between cells //cells which may lead to inconsistent results when shading between cells
int num = MWBase::Environment::get().getWorld()->getStore().get<ESM::LandTexture>().getSize(plugin);
std::set<uint16_t> ltexIndexes; std::set<uint16_t> ltexIndexes;
for ( int y = fromY - 1; y < fromY + size + 1; y++ ) for ( int y = fromY - 1; y < fromY + size + 1; y++ )
{ {
for ( int x = fromX - 1; x < fromX + size + 1; x++ ) for ( int x = fromX - 1; x < fromX + size + 1; x++ )
{ {
ltexIndexes.insert(getLtexIndexAt(cellX, cellY, x, y)); int idx = getLtexIndexAt(cellX, cellY, x, y);
// This is a quick hack to prevent the program from trying to fetch textures
// from a neighboring cell, which might originate from a different plugin,
// and use a separate texture palette. Right now, we simply cast it to the
// default texture (i.e. 0).
if (idx > num)
idx = 0;
ltexIndexes.insert(idx);
} }
} }
@ -244,7 +257,7 @@ namespace MWRender
iter != ltexIndexes.end(); iter != ltexIndexes.end();
++iter ) ++iter )
{ {
const uint16_t ltexIndex = *iter; uint16_t ltexIndex = *iter;
//this is the base texture, so we can ignore this at present //this is the base texture, so we can ignore this at present
if ( ltexIndex == baseTexture ) if ( ltexIndex == baseTexture )
{ {
@ -260,8 +273,11 @@ namespace MWRender
const MWWorld::Store<ESM::LandTexture> &ltexStore = const MWWorld::Store<ESM::LandTexture> &ltexStore =
MWBase::Environment::get().getWorld()->getStore().get<ESM::LandTexture>(); MWBase::Environment::get().getWorld()->getStore().get<ESM::LandTexture>();
assert( (int)ltexStore.getSize() >= (int)ltexIndex - 1 && // NOTE: using the quick hack above, we should no longer end up with textures indices
"LAND.VTEX must be within the bounds of the LTEX array"); // that are out of bounds. However, I haven't updated the test to a multi-palette
// system yet. We probably need more work here, so we skip it for now.
//assert( (int)ltexStore.getSize() >= (int)ltexIndex - 1 &&
//"LAND.VTEX must be within the bounds of the LTEX array");
std::string texture; std::string texture;
if ( ltexIndex == 0 ) if ( ltexIndex == 0 )
@ -270,7 +286,7 @@ namespace MWRender
} }
else else
{ {
texture = ltexStore.search(ltexIndex-1)->mTexture; texture = ltexStore.search(ltexIndex-1, plugin)->mTexture;
//TODO this is needed due to MWs messed up texture handling //TODO this is needed due to MWs messed up texture handling
texture = texture.substr(0, texture.rfind(".")) + ".dds"; texture = texture.substr(0, texture.rfind(".")) + ".dds";
} }

View file

@ -75,7 +75,7 @@ namespace MWRender{
void initTerrainTextures(Ogre::Terrain::ImportData* terrainData, void initTerrainTextures(Ogre::Terrain::ImportData* terrainData,
int cellX, int cellY, int cellX, int cellY,
int fromX, int fromY, int size, int fromX, int fromY, int size,
std::map<uint16_t, int>& indexes); std::map<uint16_t, int>& indexes, size_t plugin);
/** /**
* Creates the blend (splatting maps) for the given terrain from the ltex data. * Creates the blend (splatting maps) for the given terrain from the ltex data.

View file

@ -50,6 +50,14 @@ namespace MWScript
MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), item); MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), item);
ref.getPtr().getRefData().setCount (count); ref.getPtr().getRefData().setCount (count);
// Configure item's script variables
std::string script = MWWorld::Class::get(ref.getPtr()).getScript(ref.getPtr());
if (script != "")
{
const ESM::Script *esmscript = MWBase::Environment::get().getWorld()->getStore().get<ESM::Script>().find (script);
ref.getPtr().getRefData().setLocals(*esmscript);
}
MWWorld::Class::get (ptr).getContainerStore (ptr).add (ref.getPtr()); MWWorld::Class::get (ptr).getContainerStore (ptr).add (ref.getPtr());
} }

View file

@ -0,0 +1,41 @@
#include "locals.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/scriptmanager.hpp"
#include <components/compiler/locals.hpp>
namespace MWScript
{
void Locals::configure (const ESM::Script& script)
{
mShorts.clear();
mShorts.resize (script.mData.mNumShorts, 0);
mLongs.clear();
mLongs.resize (script.mData.mNumLongs, 0);
mFloats.clear();
mFloats.resize (script.mData.mNumFloats, 0);
}
bool Locals::setVarByInt(const std::string& script, const std::string& var, int val)
{
Compiler::Locals locals = MWBase::Environment::get().getScriptManager()->getLocals(script);
int index = locals.getIndex(var);
char type = locals.getType(var);
if(index != -1)
{
switch(type)
{
case 's':
mShorts.at (index) = val; break;
case 'l':
mLongs.at (index) = val; break;
case 'f':
mFloats.at (index) = val; break;
}
return true;
}
return false;
}
}

View file

@ -8,21 +8,16 @@
namespace MWScript namespace MWScript
{ {
struct Locals class Locals
{ {
std::vector<Interpreter::Type_Short> mShorts; public:
std::vector<Interpreter::Type_Integer> mLongs; std::vector<Interpreter::Type_Short> mShorts;
std::vector<Interpreter::Type_Float> mFloats; std::vector<Interpreter::Type_Integer> mLongs;
std::vector<Interpreter::Type_Float> mFloats;
void configure (const ESM::Script& script);
bool setVarByInt(const std::string& script, const std::string& var, int val);
void configure (const ESM::Script& script)
{
mShorts.clear();
mShorts.resize (script.mData.mNumShorts, 0);
mLongs.clear();
mLongs.resize (script.mData.mNumLongs, 0);
mFloats.clear();
mFloats.resize (script.mData.mNumFloats, 0);
}
}; };
} }

View file

@ -4,6 +4,8 @@
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
#include "../mwbase/windowmanager.hpp" #include "../mwbase/windowmanager.hpp"
#include <components/compiler/locals.hpp>
#include "inventorystore.hpp" #include "inventorystore.hpp"
#include "player.hpp" #include "player.hpp"
#include "class.hpp" #include "class.hpp"
@ -35,6 +37,8 @@ namespace MWWorld
std::string npcRace = actor.get<ESM::NPC>()->mBase->mRace; std::string npcRace = actor.get<ESM::NPC>()->mBase->mRace;
bool equipped = false;
// equip the item in the first free slot // equip the item in the first free slot
for (std::vector<int>::const_iterator slot=slots.first.begin(); for (std::vector<int>::const_iterator slot=slots.first.begin();
slot!=slots.first.end(); ++slot) slot!=slots.first.end(); ++slot)
@ -91,6 +95,7 @@ namespace MWWorld
if (slot == --slots.first.end()) if (slot == --slots.first.end())
{ {
invStore.equip(*slot, it); invStore.equip(*slot, it);
equipped = true;
break; break;
} }
@ -98,8 +103,15 @@ namespace MWWorld
{ {
// slot is not occupied // slot is not occupied
invStore.equip(*slot, it); invStore.equip(*slot, it);
equipped = true;
break; break;
} }
} }
std::string script = MWWorld::Class::get(*it).getScript(*it);
/* Set OnPCEquip Variable on item's script, if the player is equipping it, and it has a script with that variable declared */
if(equipped && actor == MWBase::Environment::get().getWorld()->getPlayer().getPlayer() && script != "")
(*it).mRefData->getLocals().setVarByInt(script, "onpcequip", 1);
} }
} }

View file

@ -84,7 +84,7 @@ MWWorld::Ptr MWWorld::Cells::getPtrAndCache (const std::string& name, Ptr::CellS
return ptr; return ptr;
} }
MWWorld::Cells::Cells (const MWWorld::ESMStore& store, ESM::ESMReader& reader) MWWorld::Cells::Cells (const MWWorld::ESMStore& store, std::vector<ESM::ESMReader>& reader)
: mStore (store), mReader (reader), : mStore (store), mReader (reader),
mIdCache (20, std::pair<std::string, Ptr::CellStore *> ("", (Ptr::CellStore*)0)), /// \todo make cache size configurable mIdCache (20, std::pair<std::string, Ptr::CellStore *> ("", (Ptr::CellStore*)0)), /// \todo make cache size configurable
mIdCacheIndex (0) mIdCacheIndex (0)
@ -119,6 +119,7 @@ MWWorld::Ptr::CellStore *MWWorld::Cells::getExterior (int x, int y)
if (result->second.mState!=Ptr::CellStore::State_Loaded) if (result->second.mState!=Ptr::CellStore::State_Loaded)
{ {
// Multiple plugin support for landscape data is much easier than for references. The last plugin wins.
result->second.load (mStore, mReader); result->second.load (mStore, mReader);
fillContainers (result->second); fillContainers (result->second);
} }

View file

@ -2,6 +2,7 @@
#define GAME_MWWORLD_CELLS_H #define GAME_MWWORLD_CELLS_H
#include <map> #include <map>
#include <list>
#include <string> #include <string>
#include "ptr.hpp" #include "ptr.hpp"
@ -19,7 +20,7 @@ namespace MWWorld
class Cells class Cells
{ {
const MWWorld::ESMStore& mStore; const MWWorld::ESMStore& mStore;
ESM::ESMReader& mReader; std::vector<ESM::ESMReader>& mReader;
std::map<std::string, CellStore> mInteriors; std::map<std::string, CellStore> mInteriors;
std::map<std::pair<int, int>, CellStore> mExteriors; std::map<std::pair<int, int>, CellStore> mExteriors;
std::vector<std::pair<std::string, CellStore *> > mIdCache; std::vector<std::pair<std::string, CellStore *> > mIdCache;
@ -36,7 +37,7 @@ namespace MWWorld
public: public:
Cells (const MWWorld::ESMStore& store, ESM::ESMReader& reader); Cells (const MWWorld::ESMStore& store, std::vector<ESM::ESMReader>& reader);
///< \todo pass the dynamic part of the ESMStore isntead (once it is written) of the whole ///< \todo pass the dynamic part of the ESMStore isntead (once it is written) of the whole
/// world /// world

View file

@ -10,13 +10,48 @@
namespace MWWorld namespace MWWorld
{ {
template <typename X>
void CellRefList<X>::load(ESM::CellRef &ref, const MWWorld::ESMStore &esmStore)
{
// Get existing reference, in case we need to overwrite it.
typename std::list<LiveRef>::iterator iter = std::find(mList.begin(), mList.end(), ref.mRefnum);
// Skip this when reference was deleted.
// TODO: Support respawning references, in this case, we need to track it somehow.
if (ref.mDeleted) {
mList.erase(iter);
return;
}
// for throwing exception on unhandled record type
const MWWorld::Store<X> &store = esmStore.get<X>();
const X *ptr = store.search(ref.mRefID);
/// \note no longer redundant - changed to Store<X>::search(), don't throw
/// an exception on miss, try to continue (that's how MW does it, anyway)
if (ptr == NULL) {
std::cout << "Warning: could not resolve cell reference " << ref.mRefID << ", trying to continue anyway" << std::endl;
} else {
if (iter != mList.end())
*iter = LiveRef(ref, ptr);
else
mList.push_back(LiveRef(ref, ptr));
}
}
template<typename X> bool operator==(const LiveCellRef<X>& ref, int pRefnum)
{
return (ref.mRef.mRefnum == pRefnum);
}
CellStore::CellStore (const ESM::Cell *cell) CellStore::CellStore (const ESM::Cell *cell)
: mCell (cell), mState (State_Unloaded) : mCell (cell), mState (State_Unloaded)
{ {
mWaterLevel = cell->mWater; mWaterLevel = cell->mWater;
} }
void CellStore::load (const MWWorld::ESMStore &store, ESM::ESMReader &esm) void CellStore::load (const MWWorld::ESMStore &store, std::vector<ESM::ESMReader> &esm)
{ {
if (mState!=State_Loaded) if (mState!=State_Loaded)
{ {
@ -31,7 +66,7 @@ namespace MWWorld
} }
} }
void CellStore::preload (const MWWorld::ESMStore &store, ESM::ESMReader &esm) void CellStore::preload (const MWWorld::ESMStore &store, std::vector<ESM::ESMReader> &esm)
{ {
if (mState==State_Unloaded) if (mState==State_Unloaded)
{ {
@ -41,57 +76,75 @@ namespace MWWorld
} }
} }
void CellStore::listRefs(const MWWorld::ESMStore &store, ESM::ESMReader &esm) void CellStore::listRefs(const MWWorld::ESMStore &store, std::vector<ESM::ESMReader> &esm)
{ {
assert (mCell); assert (mCell);
if (mCell->mContext.filename.empty()) if (mCell->mContextList.size() == 0)
return; // this is a dynamically generated cell -> skipping. return; // this is a dynamically generated cell -> skipping.
// Reopen the ESM reader and seek to the right position. // Load references from all plugins that do something with this cell.
mCell->restore (esm); for (size_t i = 0; i < mCell->mContextList.size(); i++)
ESM::CellRef ref;
// Get each reference in turn
while (mCell->getNextRef (esm, ref))
{ {
std::string lowerCase = Misc::StringUtils::lowerCase (ref.mRefID); // Reopen the ESM reader and seek to the right position.
int index = mCell->mContextList.at(i).index;
mCell->restore (esm[index], i);
mIds.push_back (lowerCase); ESM::CellRef ref;
// Get each reference in turn
while (mCell->getNextRef (esm[index], ref))
{
std::string lowerCase = Misc::StringUtils::lowerCase (ref.mRefID);
if (ref.mDeleted) {
// Right now, don't do anything. Where is "listRefs" actually used, anyway?
// Skipping for now...
continue;
}
mIds.push_back (lowerCase);
}
} }
std::sort (mIds.begin(), mIds.end()); std::sort (mIds.begin(), mIds.end());
} }
void CellStore::loadRefs(const MWWorld::ESMStore &store, ESM::ESMReader &esm) void CellStore::loadRefs(const MWWorld::ESMStore &store, std::vector<ESM::ESMReader> &esm)
{ {
assert (mCell); assert (mCell);
if (mCell->mContext.filename.empty()) if (mCell->mContextList.size() == 0)
return; // this is a dynamically generated cell -> skipping. return; // this is a dynamically generated cell -> skipping.
// Reopen the ESM reader and seek to the right position. // Load references from all plugins that do something with this cell.
mCell->restore(esm); for (size_t i = 0; i < mCell->mContextList.size(); i++)
ESM::CellRef ref;
// Get each reference in turn
while(mCell->getNextRef(esm, ref))
{ {
std::string lowerCase = Misc::StringUtils::lowerCase(ref.mRefID); // Reopen the ESM reader and seek to the right position.
int index = mCell->mContextList.at(i).index;
mCell->restore (esm[index], i);
int rec = store.find(ref.mRefID); ESM::CellRef ref;
ref.mRefID = lowerCase; // Get each reference in turn
while(mCell->getNextRef(esm[index], ref))
/* We can optimize this further by storing the pointer to the
record itself in store.all, so that we don't need to look it
up again here. However, never optimize. There are infinite
opportunities to do that later.
*/
switch(rec)
{ {
// Don't load reference if it was moved to a different cell.
std::string lowerCase = Misc::StringUtils::lowerCase(ref.mRefID);
ESM::MovedCellRefTracker::const_iterator iter = std::find(mCell->mMovedRefs.begin(), mCell->mMovedRefs.end(), ref.mRefnum);
if (iter != mCell->mMovedRefs.end()) {
continue;
}
int rec = store.find(ref.mRefID);
ref.mRefID = lowerCase;
/* We can optimize this further by storing the pointer to the
record itself in store.all, so that we don't need to look it
up again here. However, never optimize. There are infinite
opportunities to do that later.
*/
switch(rec)
{
case ESM::REC_ACTI: mActivators.load(ref, store); break; case ESM::REC_ACTI: mActivators.load(ref, store); break;
case ESM::REC_ALCH: mPotions.load(ref, store); break; case ESM::REC_ALCH: mPotions.load(ref, store); break;
case ESM::REC_APPA: mAppas.load(ref, store); break; case ESM::REC_APPA: mAppas.load(ref, store); break;
@ -113,10 +166,62 @@ namespace MWWorld
case ESM::REC_STAT: mStatics.load(ref, store); break; case ESM::REC_STAT: mStatics.load(ref, store); break;
case ESM::REC_WEAP: mWeapons.load(ref, store); break; case ESM::REC_WEAP: mWeapons.load(ref, store); break;
case 0: std::cout << "Cell reference " + ref.mRefID + " not found!\n"; break; case 0: std::cout << "Cell reference " + ref.mRefID + " not found!\n"; break;
default: default:
std::cout << "WARNING: Ignoring reference '" << ref.mRefID << "' of unhandled type\n"; std::cout << "WARNING: Ignoring reference '" << ref.mRefID << "' of unhandled type\n";
}
} }
} }
// Load moved references, from separately tracked list.
for (ESM::CellRefTracker::const_iterator it = mCell->mLeasedRefs.begin(); it != mCell->mLeasedRefs.end(); it++)
{
// Doesn't seem to work in one line... huh? Too sleepy to check...
ESM::CellRef &ref = const_cast<ESM::CellRef&>(*it);
//ESM::CellRef &ref = const_cast<ESM::CellRef&>(it->second);
std::string lowerCase;
std::transform (ref.mRefID.begin(), ref.mRefID.end(), std::back_inserter (lowerCase),
(int(*)(int)) std::tolower);
int rec = store.find(ref.mRefID);
ref.mRefID = lowerCase;
/* We can optimize this further by storing the pointer to the
record itself in store.all, so that we don't need to look it
up again here. However, never optimize. There are infinite
opportunities to do that later.
*/
switch(rec)
{
case ESM::REC_ACTI: mActivators.load(ref, store); break;
case ESM::REC_ALCH: mPotions.load(ref, store); break;
case ESM::REC_APPA: mAppas.load(ref, store); break;
case ESM::REC_ARMO: mArmors.load(ref, store); break;
case ESM::REC_BOOK: mBooks.load(ref, store); break;
case ESM::REC_CLOT: mClothes.load(ref, store); break;
case ESM::REC_CONT: mContainers.load(ref, store); break;
case ESM::REC_CREA: mCreatures.load(ref, store); break;
case ESM::REC_DOOR: mDoors.load(ref, store); break;
case ESM::REC_INGR: mIngreds.load(ref, store); break;
case ESM::REC_LEVC: mCreatureLists.load(ref, store); break;
case ESM::REC_LEVI: mItemLists.load(ref, store); break;
case ESM::REC_LIGH: mLights.load(ref, store); break;
case ESM::REC_LOCK: mLockpicks.load(ref, store); break;
case ESM::REC_MISC: mMiscItems.load(ref, store); break;
case ESM::REC_NPC_: mNpcs.load(ref, store); break;
case ESM::REC_PROB: mProbes.load(ref, store); break;
case ESM::REC_REPA: mRepairs.load(ref, store); break;
case ESM::REC_STAT: mStatics.load(ref, store); break;
case ESM::REC_WEAP: mWeapons.load(ref, store); break;
case 0: std::cout << "Cell reference " + ref.mRefID + " not found!\n"; break;
default:
std::cout << "WARNING: Ignoring reference '" << ref.mRefID << "' of unhandled type\n";
}
}
} }
} }

View file

@ -3,17 +3,18 @@
#include <components/esm/records.hpp> #include <components/esm/records.hpp>
#include <list> #include <deque>
#include <algorithm> #include <algorithm>
#include "refdata.hpp" #include "refdata.hpp"
#include "esmstore.hpp" #include "esmstore.hpp"
struct C;
namespace MWWorld namespace MWWorld
{ {
class Ptr; class Ptr;
class ESMStore; class ESMStore;
/// A reference to one object (of any type) in a cell. /// A reference to one object (of any type) in a cell.
/// ///
/// Constructing this with a CellRef instance in the constructor means that /// Constructing this with a CellRef instance in the constructor means that
@ -42,6 +43,8 @@ namespace MWWorld
/// runtime-data /// runtime-data
RefData mData; RefData mData;
}; };
template<typename X> bool operator==(const LiveCellRef<X>& ref, int pRefnum);
/// A list of cell references /// A list of cell references
template <typename X> template <typename X>
@ -51,21 +54,14 @@ namespace MWWorld
typedef std::list<LiveRef> List; typedef std::list<LiveRef> List;
List mList; List mList;
/// Searches for reference of appropriate type in given ESMStore. // Search for the given reference in the given reclist from
/// If reference exists, loads it into container, throws an exception // ESMStore. Insert the reference into the list if a match is
/// on miss // found. If not, throw an exception.
void load(ESM::CellRef &ref, const MWWorld::ESMStore &esmStore) // Moved to cpp file, as we require a custom compare operator for it,
{ // and the build will fail with an ugly three-way cyclic header dependence
// for throwing exception on unhandled record type // so we need to pass the instantiation of the method to the lnker, when
const MWWorld::Store<X> &store = esmStore.get<X>(); // all methods are known.
const X *ptr = store.find(ref.mRefID); void load(ESM::CellRef &ref, const MWWorld::ESMStore &esmStore);
/// \note redundant because Store<X>::find() throws exception on miss
if (ptr == NULL) {
throw std::runtime_error("Error resolving cell reference " + ref.mRefID);
}
mList.push_back(LiveRef(ref, ptr));
}
LiveRef *find (const std::string& name) LiveRef *find (const std::string& name)
{ {
@ -124,9 +120,9 @@ namespace MWWorld
CellRefList<ESM::Static> mStatics; CellRefList<ESM::Static> mStatics;
CellRefList<ESM::Weapon> mWeapons; CellRefList<ESM::Weapon> mWeapons;
void load (const MWWorld::ESMStore &store, ESM::ESMReader &esm); void load (const MWWorld::ESMStore &store, std::vector<ESM::ESMReader> &esm);
void preload (const MWWorld::ESMStore &store, ESM::ESMReader &esm); void preload (const MWWorld::ESMStore &store, std::vector<ESM::ESMReader> &esm);
/// Call functor (ref) for each reference. functor must return a bool. Returning /// Call functor (ref) for each reference. functor must return a bool. Returning
/// false will abort the iteration. /// false will abort the iteration.
@ -185,9 +181,9 @@ namespace MWWorld
} }
/// Run through references and store IDs /// Run through references and store IDs
void listRefs(const MWWorld::ESMStore &store, ESM::ESMReader &esm); void listRefs(const MWWorld::ESMStore &store, std::vector<ESM::ESMReader> &esm);
void loadRefs(const MWWorld::ESMStore &store, ESM::ESMReader &esm); void loadRefs(const MWWorld::ESMStore &store, std::vector<ESM::ESMReader> &esm);
}; };
} }

View file

@ -8,9 +8,11 @@
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include <components/esm/loadcont.hpp> #include <components/esm/loadcont.hpp>
#include <components/compiler/locals.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
#include "../mwbase/scriptmanager.hpp"
#include "manualref.hpp" #include "manualref.hpp"
#include "refdata.hpp" #include "refdata.hpp"
@ -83,9 +85,14 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& ptr)
CellStore *cell; CellStore *cell;
Ptr player = MWBase::Environment::get().getWorld ()->getPlayer().getPlayer(); Ptr player = MWBase::Environment::get().getWorld ()->getPlayer().getPlayer();
// Items in players inventory have cell set to 0, so their scripts will never be removed
if(&(MWWorld::Class::get (player).getContainerStore (player)) == this) if(&(MWWorld::Class::get (player).getContainerStore (player)) == this)
cell = 0; {
cell = 0; // Items in player's inventory have cell set to 0, so their scripts will never be removed
// Set OnPCAdd special variable, if it is declared
item.mRefData->getLocals().setVarByInt(script, "onpcadd", 1);
}
else else
cell = player.getCell(); cell = player.getCell();

View file

@ -3,6 +3,8 @@
#include <set> #include <set>
#include <iostream> #include <iostream>
#include <boost/filesystem/v3/operations.hpp>
namespace MWWorld namespace MWWorld
{ {
@ -25,6 +27,33 @@ void ESMStore::load(ESM::ESMReader &esm)
ESM::Dialogue *dialogue = 0; ESM::Dialogue *dialogue = 0;
// Cache parent esX files by tracking their indices in the global list of
// all files/readers used by the engine. This will greaty accelerate
// refnumber mangling, as required for handling moved references.
int index = ~0;
const ESM::ESMReader::MasterList &masters = esm.getMasters();
std::vector<ESM::ESMReader> *allPlugins = esm.getGlobalReaderList();
for (size_t j = 0; j < masters.size(); j++) {
ESM::MasterData &mast = const_cast<ESM::MasterData&>(masters[j]);
std::string fname = mast.name;
for (int i = 0; i < esm.getIndex(); i++) {
const std::string &candidate = allPlugins->at(i).getContext().filename;
std::string fnamecandidate = boost::filesystem::path(candidate).filename().string();
if (fname == fnamecandidate) {
index = i;
break;
}
}
if (index == (int)~0) {
// Tried to load a parent file that has not been loaded yet. This is bad,
// the launcher should have taken care of this.
std::string fstring = "File " + fname + " asks for parent file " + masters[j].name
+ ", but it has not been loaded yet. Please check your load order.";
esm.fail(fstring);
}
mast.index = index;
}
// Loop through all records // Loop through all records
while(esm.hasMoreRecs()) while(esm.hasMoreRecs())
{ {
@ -55,12 +84,21 @@ void ESMStore::load(ESM::ESMReader &esm)
} else { } else {
// Load it // Load it
std::string id = esm.getHNOString("NAME"); std::string id = esm.getHNOString("NAME");
// ... unless it got deleted! This means that the following record
// has been deleted, and trying to load it using standard assumptions
// on the structure will (probably) fail.
if (esm.isNextSub("DELE")) {
esm.skipRecord();
it->second->eraseStatic(id);
continue;
}
it->second->load(esm, id); it->second->load(esm, id);
if (n.val==ESM::REC_DIAL) { if (n.val==ESM::REC_DIAL) {
// dirty hack, but it is better than non-const search() // dirty hack, but it is better than non-const search()
// or friends // or friends
dialogue = &mDialogs.mStatic.back(); //dialogue = &mDialogs.mStatic.back();
dialogue = const_cast<ESM::Dialogue*>(mDialogs.find(id));
assert (dialogue->mId == id); assert (dialogue->mId == id);
} else { } else {
dialogue = 0; dialogue = 0;
@ -84,7 +122,6 @@ void ESMStore::load(ESM::ESMReader &esm)
cout << *it << " "; cout << *it << " ";
cout << endl; cout << endl;
*/ */
setUp();
} }
void ESMStore::setUp() void ESMStore::setUp()
@ -100,12 +137,11 @@ void ESMStore::setUp()
ESM::NPC item; ESM::NPC item;
item.mId = "player"; item.mId = "player";
std::vector<ESM::NPC>::iterator pIt = const ESM::NPC *pIt = mNpcs.find("player");
std::lower_bound(mNpcs.mStatic.begin(), mNpcs.mStatic.end(), item, RecordCmp()); assert(pIt != NULL);
assert(pIt != mNpcs.mStatic.end() && pIt->mId == "player");
mNpcs.insert(*pIt); mNpcs.insert(*pIt);
mNpcs.mStatic.erase(pIt); mNpcs.eraseStatic(pIt->mId);
} }
} // end namespace } // end namespace

View file

@ -94,6 +94,9 @@ namespace MWWorld
ESMStore() ESMStore()
: mDynamicCount(0) : mDynamicCount(0)
{ {
// Cell store needs access to this for tracking moved references
mCells.mEsmStore = this;
mStores[ESM::REC_ACTI] = &mActivators; mStores[ESM::REC_ACTI] = &mActivators;
mStores[ESM::REC_ALCH] = &mPotions; mStores[ESM::REC_ALCH] = &mPotions;
mStores[ESM::REC_APPA] = &mAppas; mStores[ESM::REC_APPA] = &mAppas;
@ -168,7 +171,8 @@ namespace MWWorld
return ptr; return ptr;
} }
private: // This method must be called once, after loading all master/plugin files. This can only be done
// from the outside, so it must be public.
void setUp(); void setUp();
}; };

View file

@ -0,0 +1,65 @@
#include "store.hpp"
namespace MWWorld {
void Store<ESM::Cell>::load(ESM::ESMReader &esm, const std::string &id)
{
// Don't automatically assume that a new cell must be spawned. Multiple plugins write to the same cell,
// and we merge all this data into one Cell object. However, we can't simply search for the cell id,
// as many exterior cells do not have a name. Instead, we need to search by (x,y) coordinates - and they
// are not available until both cells have been loaded! So first, proceed as usual.
// All cells have a name record, even nameless exterior cells.
std::string idLower = Misc::StringUtils::lowerCase(id);
ESM::Cell *cell = new ESM::Cell;
cell->mName = id;
// The cell itself takes care of some of the hairy details
cell->load(esm, *mEsmStore);
if(cell->mData.mFlags & ESM::Cell::Interior)
{
// Store interior cell by name, try to merge with existing parent data.
ESM::Cell *oldcell = const_cast<ESM::Cell*>(search(idLower));
if (oldcell) {
// push the new references on the list of references to manage
oldcell->mContextList.push_back(cell->mContextList.at(0));
// copy list into new cell
cell->mContextList = oldcell->mContextList;
// have new cell replace old cell
*oldcell = *cell;
} else
mInt[idLower] = *cell;
}
else
{
// Store exterior cells by grid position, try to merge with existing parent data.
ESM::Cell *oldcell = const_cast<ESM::Cell*>(search(cell->getGridX(), cell->getGridY()));
if (oldcell) {
// push the new references on the list of references to manage
oldcell->mContextList.push_back(cell->mContextList.at(0));
// copy list into new cell
cell->mContextList = oldcell->mContextList;
// merge lists of leased references, use newer data in case of conflict
for (ESM::MovedCellRefTracker::const_iterator it = cell->mMovedRefs.begin(); it != cell->mMovedRefs.end(); it++) {
// remove reference from current leased ref tracker and add it to new cell
ESM::MovedCellRefTracker::iterator itold = std::find(oldcell->mMovedRefs.begin(), oldcell->mMovedRefs.end(), it->mRefnum);
if (itold != oldcell->mMovedRefs.end()) {
ESM::MovedCellRef target0 = *itold;
ESM::Cell *wipecell = const_cast<ESM::Cell*>(search(target0.mTarget[0], target0.mTarget[1]));
ESM::CellRefTracker::iterator it_lease = std::find(wipecell->mLeasedRefs.begin(), wipecell->mLeasedRefs.end(), it->mRefnum);
wipecell->mLeasedRefs.erase(it_lease);
*itold = *it;
}
}
cell->mMovedRefs = oldcell->mMovedRefs;
// have new cell replace old cell
*oldcell = *cell;
} else
mExt[std::make_pair(cell->mData.mX, cell->mData.mY)] = *cell;
}
delete cell;
}
}

View file

@ -19,6 +19,8 @@ namespace MWWorld
virtual int getSize() const = 0; virtual int getSize() const = 0;
virtual void load(ESM::ESMReader &esm, const std::string &id) = 0; virtual void load(ESM::ESMReader &esm, const std::string &id) = 0;
virtual bool eraseStatic(const std::string &id) {return false;}
}; };
template <class T> template <class T>
@ -85,7 +87,7 @@ namespace MWWorld
template <class T> template <class T>
class Store : public StoreBase class Store : public StoreBase
{ {
std::vector<T> mStatic; std::map<std::string, T> mStatic;
std::vector<T *> mShared; std::vector<T *> mShared;
std::map<std::string, T> mDynamic; std::map<std::string, T> mDynamic;
@ -107,11 +109,10 @@ namespace MWWorld
T item; T item;
item.mId = Misc::StringUtils::lowerCase(id); item.mId = Misc::StringUtils::lowerCase(id);
typename std::vector<T>::const_iterator it = typename std::map<std::string, T>::const_iterator it = mStatic.find(item.mId);
std::lower_bound(mStatic.begin(), mStatic.end(), item, RecordCmp());
if (it != mStatic.end() && Misc::StringUtils::ciEqual(it->second.mId, id)) {
if (it != mStatic.end() && Misc::StringUtils::ciEqual(it->mId, id)) { return &(it->second);
return &(*it);
} }
typename Dynamic::const_iterator dit = mDynamic.find(item.mId); typename Dynamic::const_iterator dit = mDynamic.find(item.mId);
@ -133,18 +134,19 @@ namespace MWWorld
} }
void load(ESM::ESMReader &esm, const std::string &id) { void load(ESM::ESMReader &esm, const std::string &id) {
mStatic.push_back(T()); std::string idLower = Misc::StringUtils::lowerCase(id);
mStatic.back().mId = Misc::StringUtils::lowerCase(id); mStatic[idLower] = T();
mStatic.back().load(esm); mStatic[idLower].mId = idLower;
mStatic[idLower].load(esm);
} }
void setUp() { void setUp() {
std::sort(mStatic.begin(), mStatic.end(), RecordCmp()); //std::sort(mStatic.begin(), mStatic.end(), RecordCmp());
mShared.reserve(mStatic.size()); mShared.reserve(mStatic.size());
typename std::vector<T>::iterator it = mStatic.begin(); typename std::map<std::string, T>::iterator it = mStatic.begin();
for (; it != mStatic.end(); ++it) { for (; it != mStatic.end(); ++it) {
mShared.push_back(&(*it)); mShared.push_back(&(it->second));
} }
} }
@ -181,6 +183,19 @@ namespace MWWorld
return ptr; return ptr;
} }
bool eraseStatic(const std::string &id) {
T item;
item.mId = Misc::StringUtils::lowerCase(id);
typename std::map<std::string, T>::iterator it = mStatic.find(item.mId);
if (it != mStatic.end() && Misc::StringUtils::ciEqual(it->second.mId, id)) {
mStatic.erase(it);
}
return true;
}
bool erase(const std::string &id) { bool erase(const std::string &id) {
std::string key = Misc::StringUtils::lowerCase(id); std::string key = Misc::StringUtils::lowerCase(id);
typename Dynamic::iterator it = mDynamic.find(key); typename Dynamic::iterator it = mDynamic.find(key);
@ -204,39 +219,48 @@ namespace MWWorld
template <> template <>
inline void Store<ESM::Dialogue>::load(ESM::ESMReader &esm, const std::string &id) { inline void Store<ESM::Dialogue>::load(ESM::ESMReader &esm, const std::string &id) {
mStatic.push_back(ESM::Dialogue()); std::string idLower = Misc::StringUtils::lowerCase(id);
mStatic.back().mId = id; mStatic[idLower] = ESM::Dialogue();
mStatic.back().load(esm); mStatic[idLower].mId = id; // don't smash case here, as this line is printed... I think
mStatic[idLower].load(esm);
} }
template <> template <>
inline void Store<ESM::Script>::load(ESM::ESMReader &esm, const std::string &id) { inline void Store<ESM::Script>::load(ESM::ESMReader &esm, const std::string &id) {
mStatic.push_back(ESM::Script()); ESM::Script scpt;
mStatic.back().load(esm); scpt.load(esm);
Misc::StringUtils::toLower(mStatic.back().mId); Misc::StringUtils::toLower(scpt.mId);
mStatic[scpt.mId] = scpt;
} }
template <> template <>
class Store<ESM::LandTexture> : public StoreBase class Store<ESM::LandTexture> : public StoreBase
{ {
std::vector<ESM::LandTexture> mStatic; // For multiple ESM/ESP files we need one list per file.
typedef std::vector<ESM::LandTexture> LandTextureList;
std::vector<LandTextureList> mStatic;
public: public:
Store<ESM::LandTexture>() { Store<ESM::LandTexture>() {
mStatic.reserve(128); mStatic.push_back(LandTextureList());
LandTextureList &ltexl = mStatic[0];
// More than enough to hold Morrowind.esm. Extra lists for plugins will we
// added on-the-fly in a different method.
ltexl.reserve(128);
} }
typedef std::vector<ESM::LandTexture>::const_iterator iterator; typedef std::vector<ESM::LandTexture>::const_iterator iterator;
const ESM::LandTexture *search(size_t index) const { const ESM::LandTexture *search(size_t index, size_t plugin) const {
if (index < mStatic.size()) { assert(plugin < mStatic.size());
return &mStatic.at(index); const LandTextureList &ltexl = mStatic[plugin];
}
return 0; assert(index < ltexl.size());
return &ltexl.at(index);
} }
const ESM::LandTexture *find(size_t index) const { const ESM::LandTexture *find(size_t index, size_t plugin) const {
const ESM::LandTexture *ptr = search(index); const ESM::LandTexture *ptr = search(index, plugin);
if (ptr == 0) { if (ptr == 0) {
std::ostringstream msg; std::ostringstream msg;
msg << "Land texture with index " << index << " not found"; msg << "Land texture with index " << index << " not found";
@ -249,23 +273,40 @@ namespace MWWorld
return mStatic.size(); return mStatic.size();
} }
void load(ESM::ESMReader &esm, const std::string &id) { int getSize(size_t plugin) const {
ESM::LandTexture ltex; assert(plugin < mStatic.size());
ltex.load(esm); return mStatic[plugin].size();
}
if (ltex.mIndex >= (int) mStatic.size()) { void load(ESM::ESMReader &esm, const std::string &id, size_t plugin) {
mStatic.resize(ltex.mIndex + 1); ESM::LandTexture lt;
lt.load(esm);
lt.mId = id;
// Make sure we have room for the structure
if (plugin >= mStatic.size()) {
mStatic.resize(plugin+1);
} }
mStatic[ltex.mIndex] = ltex; LandTextureList &ltexl = mStatic[plugin];
mStatic[ltex.mIndex].mId = id; if(lt.mIndex + 1 > (int)ltexl.size())
ltexl.resize(lt.mIndex+1);
// Store it
ltexl[lt.mIndex] = lt;
} }
iterator begin() const { void load(ESM::ESMReader &esm, const std::string &id) {
return mStatic.begin(); load(esm, id, esm.getIndex());
} }
iterator end() const { iterator begin(size_t plugin) const {
return mStatic.end(); assert(plugin < mStatic.size());
return mStatic[plugin].begin();
}
iterator end(size_t plugin) const {
assert(plugin < mStatic.size());
return mStatic[plugin].end();
} }
}; };
@ -357,19 +398,18 @@ namespace MWWorld
} }
}; };
std::vector<ESM::Cell> mInt; typedef std::map<std::string, ESM::Cell> DynamicInt;
std::vector<ESM::Cell> mExt; typedef std::map<std::pair<int, int>, ESM::Cell> DynamicExt;
DynamicInt mInt;
DynamicExt mExt;
std::vector<ESM::Cell *> mSharedInt; std::vector<ESM::Cell *> mSharedInt;
std::vector<ESM::Cell *> mSharedExt; std::vector<ESM::Cell *> mSharedExt;
typedef std::map<std::string, ESM::Cell> DynamicInt;
typedef std::map<std::pair<int, int>, ESM::Cell> DynamicExt;
DynamicInt mDynamicInt; DynamicInt mDynamicInt;
DynamicExt mDynamicExt; DynamicExt mDynamicExt;
const ESM::Cell *search(const ESM::Cell &cell) const { const ESM::Cell *search(const ESM::Cell &cell) const {
if (cell.isExterior()) { if (cell.isExterior()) {
return search(cell.getGridX(), cell.getGridY()); return search(cell.getGridX(), cell.getGridY());
@ -378,6 +418,8 @@ namespace MWWorld
} }
public: public:
ESMStore *mEsmStore;
typedef SharedIterator<ESM::Cell> iterator; typedef SharedIterator<ESM::Cell> iterator;
Store<ESM::Cell>() Store<ESM::Cell>()
@ -387,11 +429,10 @@ namespace MWWorld
ESM::Cell cell; ESM::Cell cell;
cell.mName = Misc::StringUtils::lowerCase(id); cell.mName = Misc::StringUtils::lowerCase(id);
std::vector<ESM::Cell>::const_iterator it = std::map<std::string, ESM::Cell>::const_iterator it = mInt.find(cell.mName);
std::lower_bound(mInt.begin(), mInt.end(), cell, RecordCmp());
if (it != mInt.end() && Misc::StringUtils::ciEqual(it->mName, id)) { if (it != mInt.end() && Misc::StringUtils::ciEqual(it->second.mName, id)) {
return &(*it); return &(it->second);
} }
DynamicInt::const_iterator dit = mDynamicInt.find(cell.mName); DynamicInt::const_iterator dit = mDynamicInt.find(cell.mName);
@ -406,14 +447,12 @@ namespace MWWorld
ESM::Cell cell; ESM::Cell cell;
cell.mData.mX = x, cell.mData.mY = y; cell.mData.mX = x, cell.mData.mY = y;
std::vector<ESM::Cell>::const_iterator it = std::pair<int, int> key(x, y);
std::lower_bound(mExt.begin(), mExt.end(), cell, ExtCmp()); std::map<std::pair<int, int>, ESM::Cell>::const_iterator it = mExt.find(key);
if (it != mExt.end()) {
if (it != mExt.end() && it->mData.mX == x && it->mData.mY == y) { return &(it->second);
return &(*it);
} }
std::pair<int, int> key(x, y);
DynamicExt::const_iterator dit = mDynamicExt.find(key); DynamicExt::const_iterator dit = mDynamicExt.find(key);
if (dit != mDynamicExt.end()) { if (dit != mDynamicExt.end()) {
return &dit->second; return &dit->second;
@ -422,6 +461,30 @@ namespace MWWorld
return 0; return 0;
} }
const ESM::Cell *searchOrCreate(int x, int y) {
ESM::Cell cell;
cell.mData.mX = x, cell.mData.mY = y;
std::pair<int, int> key(x, y);
std::map<std::pair<int, int>, ESM::Cell>::const_iterator it = mExt.find(key);
if (it != mExt.end()) {
return &(it->second);
}
DynamicExt::const_iterator dit = mDynamicExt.find(key);
if (dit != mDynamicExt.end()) {
return &dit->second;
}
ESM::Cell *newCell = new ESM::Cell;
newCell->mData.mX = x;
newCell->mData.mY = y;
mExt[std::make_pair(x, y)] = *newCell;
delete newCell;
return &mExt[std::make_pair(x, y)];
}
const ESM::Cell *find(const std::string &id) const { const ESM::Cell *find(const std::string &id) const {
const ESM::Cell *ptr = search(id); const ESM::Cell *ptr = search(id);
if (ptr == 0) { if (ptr == 0) {
@ -443,33 +506,29 @@ namespace MWWorld
} }
void setUp() { void setUp() {
typedef std::vector<ESM::Cell>::iterator Iterator; //typedef std::vector<ESM::Cell>::iterator Iterator;
typedef std::map<std::pair<int, int>, ESM::Cell>::iterator ExtIterator;
typedef std::map<std::string, ESM::Cell>::iterator IntIterator;
std::sort(mInt.begin(), mInt.end(), RecordCmp()); //std::sort(mInt.begin(), mInt.end(), RecordCmp());
mSharedInt.reserve(mInt.size()); mSharedInt.reserve(mInt.size());
for (Iterator it = mInt.begin(); it != mInt.end(); ++it) { for (IntIterator it = mInt.begin(); it != mInt.end(); ++it) {
mSharedInt.push_back(&(*it)); mSharedInt.push_back(&(it->second));
} }
std::sort(mExt.begin(), mExt.end(), ExtCmp()); //std::sort(mExt.begin(), mExt.end(), ExtCmp());
mSharedExt.reserve(mExt.size()); mSharedExt.reserve(mExt.size());
for (Iterator it = mExt.begin(); it != mExt.end(); ++it) { for (ExtIterator it = mExt.begin(); it != mExt.end(); ++it) {
mSharedExt.push_back(&(*it)); mSharedExt.push_back(&(it->second));
}
}
void load(ESM::ESMReader &esm, const std::string &id) {
ESM::Cell cell;
cell.mName = id;
cell.load(esm);
if (cell.isExterior()) {
mExt.push_back(cell);
} else {
mInt.push_back(cell);
} }
} }
// HACK: Method implementation had to be moved to a separate cpp file, as we would otherwise get
// errors related to the compare operator used in std::find for ESM::MovedCellRefTracker::find.
// There some nasty three-way cyclic header dependency involved, which I could only fix by moving
// this method.
void load(ESM::ESMReader &esm, const std::string &id);
iterator intBegin() const { iterator intBegin() const {
return iterator(mSharedInt.begin()); return iterator(mSharedInt.begin());
} }

View file

@ -2,11 +2,13 @@
#include <components/bsa/bsa_archive.hpp> #include <components/bsa/bsa_archive.hpp>
#include <components/files/collections.hpp> #include <components/files/collections.hpp>
#include <components/compiler/locals.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/soundmanager.hpp" #include "../mwbase/soundmanager.hpp"
#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/windowmanager.hpp" #include "../mwbase/windowmanager.hpp"
#include "../mwbase/scriptmanager.hpp"
#include "../mwrender/sky.hpp" #include "../mwrender/sky.hpp"
#include "../mwrender/player.hpp" #include "../mwrender/player.hpp"
@ -53,7 +55,7 @@ namespace
for (iterator iter (refList.mList.begin()); iter!=refList.mList.end(); ++iter) for (iterator iter (refList.mList.begin()); iter!=refList.mList.end(); ++iter)
{ {
if(iter->mData.getCount() > 0 && iter->mData.getBaseNode()){ if (iter->mData.getCount() > 0 && iter->mData.getBaseNode()){
if (iter->mData.getHandle()==handle) if (iter->mData.getHandle()==handle)
{ {
return &*iter; return &*iter;
@ -169,7 +171,8 @@ namespace MWWorld
World::World (OEngine::Render::OgreRenderer& renderer, World::World (OEngine::Render::OgreRenderer& renderer,
const Files::Collections& fileCollections, const Files::Collections& fileCollections,
const std::string& master, const boost::filesystem::path& resDir, const boost::filesystem::path& cacheDir, bool newGame, const std::vector<std::string>& master, const std::vector<std::string>& plugins,
const boost::filesystem::path& resDir, const boost::filesystem::path& cacheDir, bool newGame,
ToUTF8::Utf8Encoder* encoder, std::map<std::string,std::string> fallbackMap, int mActivationDistanceOverride) ToUTF8::Utf8Encoder* encoder, std::map<std::string,std::string> fallbackMap, int mActivationDistanceOverride)
: mPlayer (0), mLocalScripts (mStore), mGlobalVariables (0), : mPlayer (0), mLocalScripts (mStore), mGlobalVariables (0),
mSky (true), mCells (mStore, mEsm), mSky (true), mCells (mStore, mEsm),
@ -182,14 +185,42 @@ namespace MWWorld
mWeatherManager = new MWWorld::WeatherManager(mRendering); mWeatherManager = new MWWorld::WeatherManager(mRendering);
boost::filesystem::path masterPath (fileCollections.getCollection (".esm").getPath (master)); int idx = 0;
// NOTE: We might need to reserve one more for the running game / save.
mEsm.resize(master.size() + plugins.size());
for (std::vector<std::string>::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";
std::cout << "Loading ESM " << masterPath.string() << "\n"; // 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]);
}
for (std::vector<std::string>::size_type i = 0; i < plugins.size(); i++, idx++)
{
boost::filesystem::path pluginPath (fileCollections.getCollection (".esp").getPath (plugins[i]));
std::cout << "Loading ESP " << pluginPath.string() << "\n";
// This parses the ESM file and loads a sample cell // This parses the ESP file
mEsm.setEncoder(encoder); ESM::ESMReader lEsm;
mEsm.open (masterPath.string()); lEsm.setEncoder(encoder);
mStore.load (mEsm); lEsm.setIndex(idx);
lEsm.setGlobalReaderList(&mEsm);
lEsm.open (pluginPath.string());
mEsm[idx] = lEsm;
mStore.load (mEsm[idx]);
}
mStore.setUp();
mPlayer = new MWWorld::Player (mStore.get<ESM::NPC>().find ("player"), *this); mPlayer = new MWWorld::Player (mStore.get<ESM::NPC>().find ("player"), *this);
mRendering->attachCameraTo(mPlayer->getPlayer()); mRendering->attachCameraTo(mPlayer->getPlayer());
@ -268,7 +299,7 @@ namespace MWWorld
return mStore; return mStore;
} }
ESM::ESMReader& World::getEsmReader() std::vector<ESM::ESMReader>& World::getEsmReader()
{ {
return mEsm; return mEsm;
} }
@ -698,6 +729,8 @@ namespace MWWorld
if (*currCell != newCell) if (*currCell != newCell)
{ {
removeContainerScripts(ptr);
if (isPlayer) if (isPlayer)
if (!newCell.isExterior()) if (!newCell.isExterior())
changeToInteriorCell(Misc::StringUtils::lowerCase(newCell.mCell->mName), pos); changeToInteriorCell(Misc::StringUtils::lowerCase(newCell.mCell->mName), pos);
@ -1229,8 +1262,8 @@ namespace MWWorld
std::vector<World::DoorMarker> result; std::vector<World::DoorMarker> result;
MWWorld::CellRefList<ESM::Door>& doors = cell->mDoors; MWWorld::CellRefList<ESM::Door>& doors = cell->mDoors;
std::list< MWWorld::LiveCellRef<ESM::Door> >& refList = doors.mList; CellRefList<ESM::Door>::List& refList = doors.mList;
for (std::list< MWWorld::LiveCellRef<ESM::Door> >::iterator it = refList.begin(); it != refList.end(); ++it) for (CellRefList<ESM::Door>::List::iterator it = refList.begin(); it != refList.end(); ++it)
{ {
MWWorld::LiveCellRef<ESM::Door>& ref = *it; MWWorld::LiveCellRef<ESM::Door>& ref = *it;
@ -1270,6 +1303,15 @@ namespace MWWorld
mRendering->toggleWater(); mRendering->toggleWater();
} }
void World::PCDropped (const Ptr& item)
{
std::string script = MWWorld::Class::get(item).getScript(item);
// Set OnPCDrop Variable on item's script, if it has a script with that variable declared
if(script != "")
item.mRefData->getLocals().setVarByInt(script, "onpcdrop", 1);
}
bool World::placeObject (const Ptr& object, float cursorX, float cursorY) bool World::placeObject (const Ptr& object, float cursorX, float cursorY)
{ {
std::pair<bool, Ogre::Vector3> result = mPhysics->castRay(cursorX, cursorY); std::pair<bool, Ogre::Vector3> result = mPhysics->castRay(cursorX, cursorY);
@ -1292,7 +1334,8 @@ namespace MWWorld
pos.pos[1] = -result.second[2]; pos.pos[1] = -result.second[2];
pos.pos[2] = result.second[1]; pos.pos[2] = result.second[1];
copyObjectToCell(object, *cell, pos); Ptr dropped = copyObjectToCell(object, *cell, pos);
PCDropped(dropped);
object.getRefData().setCount(0); object.getRefData().setCount(0);
return true; return true;
@ -1309,8 +1352,8 @@ namespace MWWorld
return true; return true;
} }
void
World::copyObjectToCell(const Ptr &object, CellStore &cell, const ESM::Position &pos) Ptr World::copyObjectToCell(const Ptr &object, CellStore &cell, const ESM::Position &pos)
{ {
/// \todo add searching correct cell for position specified /// \todo add searching correct cell for position specified
MWWorld::Ptr dropped = MWWorld::Ptr dropped =
@ -1334,6 +1377,8 @@ namespace MWWorld
} }
addContainerScripts(dropped, &cell); addContainerScripts(dropped, &cell);
} }
return dropped;
} }
void World::dropObjectOnGround (const Ptr& actor, const Ptr& object) void World::dropObjectOnGround (const Ptr& actor, const Ptr& object)
@ -1354,7 +1399,9 @@ namespace MWWorld
mPhysics->castRay(orig, dir, len); mPhysics->castRay(orig, dir, len);
pos.pos[2] = hit.second.z; pos.pos[2] = hit.second.z;
copyObjectToCell(object, *cell, pos); Ptr dropped = copyObjectToCell(object, *cell, pos);
if(actor == mPlayer->getPlayer()) // Only call if dropped by player
PCDropped(dropped);
object.getRefData().setCount(0); object.getRefData().setCount(0);
} }

View file

@ -54,7 +54,7 @@ namespace MWWorld
MWWorld::Scene *mWorldScene; MWWorld::Scene *mWorldScene;
MWWorld::Player *mPlayer; MWWorld::Player *mPlayer;
ESM::ESMReader mEsm; std::vector<ESM::ESMReader> mEsm;
MWWorld::ESMStore mStore; MWWorld::ESMStore mStore;
LocalScripts mLocalScripts; LocalScripts mLocalScripts;
MWWorld::Globals *mGlobalVariables; MWWorld::Globals *mGlobalVariables;
@ -91,8 +91,8 @@ namespace MWWorld
bool moveObjectImp (const Ptr& ptr, float x, float y, float z); bool moveObjectImp (const Ptr& ptr, float x, float y, float z);
///< @return true if the active cell (cell player is in) changed ///< @return true if the active cell (cell player is in) changed
virtual void
copyObjectToCell(const Ptr &ptr, CellStore &cell, const ESM::Position &pos); Ptr copyObjectToCell(const Ptr &ptr, CellStore &cell, const ESM::Position &pos);
void updateWindowManager (); void updateWindowManager ();
void performUpdateSceneQueries (); void performUpdateSceneQueries ();
@ -107,12 +107,14 @@ namespace MWWorld
void removeContainerScripts(const Ptr& reference); void removeContainerScripts(const Ptr& reference);
void addContainerScripts(const Ptr& reference, Ptr::CellStore* cell); void addContainerScripts(const Ptr& reference, Ptr::CellStore* cell);
void PCDropped (const Ptr& item);
public: public:
World (OEngine::Render::OgreRenderer& renderer, World (OEngine::Render::OgreRenderer& renderer,
const Files::Collections& fileCollections, const Files::Collections& fileCollections,
const std::string& master, const boost::filesystem::path& resDir, const boost::filesystem::path& cacheDir, bool newGame, const std::vector<std::string>& master, const std::vector<std::string>& plugins,
const boost::filesystem::path& resDir, const boost::filesystem::path& cacheDir, bool newGame,
ToUTF8::Utf8Encoder* encoder, std::map<std::string,std::string> fallbackMap, int mActivationDistanceOverride); ToUTF8::Utf8Encoder* encoder, std::map<std::string,std::string> fallbackMap, int mActivationDistanceOverride);
virtual ~World(); virtual ~World();
@ -142,7 +144,7 @@ namespace MWWorld
virtual const MWWorld::ESMStore& getStore() const; virtual const MWWorld::ESMStore& getStore() const;
virtual ESM::ESMReader& getEsmReader(); virtual std::vector<ESM::ESMReader>& getEsmReader();
virtual LocalScripts& getLocalScripts(); virtual LocalScripts& getLocalScripts();

View file

@ -23,6 +23,22 @@ endforeach (u)
source_group ("components\\${dir}" FILES ${files}) source_group ("components\\${dir}" FILES ${files})
endmacro (add_component_dir) endmacro (add_component_dir)
macro (add_component_qt_dir dir)
set (files)
foreach (u ${ARGN})
file (GLOB ALL ${CMAKE_CURRENT_SOURCE_DIR} "${dir}/${u}.[ch]pp")
foreach (f ${ALL})
list (APPEND files "${f}")
list (APPEND COMPONENT_FILES "${f}")
endforeach (f)
file (GLOB MOC_H ${CMAKE_CURRENT_SOURCE_DIR} "${dir}/${u}.hpp")
foreach (fi ${MOC_H})
list (APPEND COMPONENT_MOC_FILES "${fi}")
endforeach (fi)
endforeach (u)
source_group ("components\\${dir}" FILES ${files})
endmacro (add_component_qt_dir)
macro (copy_all_files source_dir destination_dir files) macro (copy_all_files source_dir destination_dir files)
foreach (f ${files}) foreach (f ${files})
get_filename_component(filename ${f} NAME) get_filename_component(filename ${f} NAME)

View file

@ -66,9 +66,21 @@ add_component_dir (translation
translation translation
) )
find_package(Qt4 COMPONENTS QtCore QtGui)
if(QT_QTGUI_LIBRARY AND QT_QTCORE_LIBRARY)
add_component_qt_dir (fileorderlist
datafileslist model/modelitem model/datafilesmodel model/esm/esmfile
utils/filedialog utils/lineedit utils/naturalsort
)
include(${QT_USE_FILE})
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})
add_library(components STATIC ${COMPONENT_FILES}) add_library(components STATIC ${COMPONENT_FILES} ${MOC_SRCS})
target_link_libraries(components ${Boost_LIBRARIES} ${OGRE_LIBRARIES}) target_link_libraries(components ${Boost_LIBRARIES} ${OGRE_LIBRARIES})

View file

@ -89,6 +89,7 @@ struct MasterData
{ {
std::string name; std::string name;
uint64_t size; uint64_t size;
int index; // Position of the parent file in the global list of loaded files
}; };
// Data that is only present in save game files // Data that is only present in save game files
@ -113,6 +114,10 @@ struct ESM_Context
size_t leftFile; size_t leftFile;
NAME recName, subName; NAME recName, subName;
HEDRstruct header; HEDRstruct header;
// When working with multiple esX files, we will generate lists of all files that
// actually contribute to a specific cell. Therefore, we need to store the index
// of the file belonging to this contest. See CellStore::(list/load)refs for details.
int index;
// True if subName has been read but not used. // True if subName has been read but not used.
bool subCached; bool subCached;

View file

@ -78,6 +78,17 @@ public:
void openRaw(const std::string &file); void openRaw(const std::string &file);
// This is a quick hack for multiple esm/esp files. Each plugin introduces its own
// terrain palette, but ESMReader does not pass a reference to the correct plugin
// to the individual load() methods. This hack allows to pass this reference
// indirectly to the load() method.
int mIdx;
void setIndex(const int index) {mIdx = index; mCtx.index = index;}
const int getIndex() {return mIdx;}
void setGlobalReaderList(std::vector<ESMReader> *list) {mGlobalReaderList = list;}
std::vector<ESMReader> *getGlobalReaderList() {return mGlobalReaderList;}
/************************************************************************* /*************************************************************************
* *
* Medium-level reading shortcuts * Medium-level reading shortcuts
@ -110,6 +121,14 @@ public:
getHT(x); getHT(x);
} }
template <typename X>
void getHNOT(X &x, const char* name, int size)
{
assert(sizeof(X) == size);
if(isNextSub(name))
getHT(x);
}
int64_t getHNLong(const char *name); int64_t getHNLong(const char *name);
// Get data of a given type/size, including subrecord header // Get data of a given type/size, including subrecord header
@ -251,6 +270,7 @@ private:
SaveData mSaveData; SaveData mSaveData;
MasterList mMasters; MasterList mMasters;
std::vector<ESMReader> *mGlobalReaderList;
ToUTF8::Utf8Encoder* mEncoder; ToUTF8::Utf8Encoder* mEncoder;
}; };
} }

View file

@ -2,13 +2,29 @@
#include <string> #include <string>
#include <sstream> #include <sstream>
#include <list>
#include <boost/concept_check.hpp>
#include "esmreader.hpp" #include "esmreader.hpp"
#include "esmwriter.hpp" #include "esmwriter.hpp"
#include <apps/openmw/mwworld/store.hpp>
#include <apps/openmw/mwworld/cellstore.hpp>
namespace ESM namespace ESM
{ {
/// Some overloaded copare operators.
bool operator==(const MovedCellRef& ref, int pRefnum)
{
return (ref.mRefnum == pRefnum);
}
bool operator==(const CellRef& ref, int pRefnum)
{
return (ref.mRefnum == pRefnum);
}
void CellRef::save(ESMWriter &esm) void CellRef::save(ESMWriter &esm)
{ {
esm.writeHNT("FRMR", mRefnum); esm.writeHNT("FRMR", mRefnum);
@ -63,10 +79,11 @@ void CellRef::save(ESMWriter &esm)
} }
} }
void Cell::load(ESMReader &esm) void Cell::load(ESMReader &esm, MWWorld::ESMStore &store)
{ {
// Ignore this for now, it might mean we should delete the entire // Ignore this for now, it might mean we should delete the entire
// cell? // cell?
// TODO: treat the special case "another plugin moved this ref, but we want to delete it"!
if (esm.isNextSub("DELE")) { if (esm.isNextSub("DELE")) {
esm.skipHSub(); esm.skipHSub();
} }
@ -109,9 +126,37 @@ void Cell::load(ESMReader &esm)
if (esm.isNextSub("NAM0")) { if (esm.isNextSub("NAM0")) {
esm.getHT(mNAM0); esm.getHT(mNAM0);
} }
// preload moved references
while (esm.isNextSub("MVRF")) {
CellRef ref;
MovedCellRef cMRef;
getNextMVRF(esm, cMRef);
MWWorld::Store<ESM::Cell> &cStore = const_cast<MWWorld::Store<ESM::Cell>&>(store.get<ESM::Cell>());
ESM::Cell *cellAlt = const_cast<ESM::Cell*>(cStore.searchOrCreate(cMRef.mTarget[0], cMRef.mTarget[1]));
// Get regular moved reference data. Adapted from CellStore::loadRefs. Maybe we can optimize the following
// implementation when the oher implementation works as well.
getNextRef(esm, ref);
std::string lowerCase;
std::transform (ref.mRefID.begin(), ref.mRefID.end(), std::back_inserter (lowerCase),
(int(*)(int)) std::tolower);
// Add data required to make reference appear in the correct cell.
// We should not need to test for duplicates, as this part of the code is pre-cell merge.
mMovedRefs.push_back(cMRef);
// But there may be duplicates here!
ESM::CellRefTracker::iterator iter = std::find(cellAlt->mLeasedRefs.begin(), cellAlt->mLeasedRefs.end(), ref.mRefnum);
if (iter == cellAlt->mLeasedRefs.end())
cellAlt->mLeasedRefs.push_back(ref);
else
*iter = ref;
}
// Save position of the cell references and move on // Save position of the cell references and move on
mContext = esm.getContext(); mContextList.push_back(esm.getContext());
esm.skipRecord(); esm.skipRecord();
} }
@ -146,9 +191,9 @@ void Cell::save(ESMWriter &esm)
esm.writeHNT("NAM0", mNAM0); esm.writeHNT("NAM0", mNAM0);
} }
void Cell::restore(ESMReader &esm) const void Cell::restore(ESMReader &esm, int iCtx) const
{ {
esm.restoreContext(mContext); esm.restoreContext(mContextList[iCtx]);
} }
std::string Cell::getDescription() const std::string Cell::getDescription() const
@ -167,17 +212,61 @@ std::string Cell::getDescription() const
bool Cell::getNextRef(ESMReader &esm, CellRef &ref) bool Cell::getNextRef(ESMReader &esm, CellRef &ref)
{ {
// TODO: Try and document reference numbering, I don't think this has been done anywhere else.
if (!esm.hasMoreSubs()) if (!esm.hasMoreSubs())
return false; return false;
// NOTE: We should not need this check. It is a safety check until we have checked
// more plugins, and how they treat these moved references.
if (esm.isNextSub("MVRF")) {
esm.skipRecord(); // skip MVRF
esm.skipRecord(); // skip CNDT
// That should be it, I haven't seen any other fields yet.
}
esm.getHNT(ref.mRefnum, "FRMR"); esm.getHNT(ref.mRefnum, "FRMR");
ref.mRefID = esm.getHNString("NAME"); ref.mRefID = esm.getHNString("NAME");
// Identify references belonging to a parent file and adapt the ID accordingly.
int local = (ref.mRefnum & 0xff000000) >> 24;
size_t global = esm.getIndex() + 1;
if (local)
{
// 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 ESM::ESMReader::MasterList &masters = esm.getMasters();
global = masters[local-1].index + 1;
ref.mRefnum |= global << 24; // insert global plugin ID
}
else
{
// This is an addition by the present plugin. Set the corresponding plugin index.
ref.mRefnum |= global << 24; // insert global plugin ID
}
// getHNOT will not change the existing value if the subrecord is // getHNOT will not change the existing value if the subrecord is
// missing // missing
ref.mScale = 1.0; ref.mScale = 1.0;
esm.getHNOT(ref.mScale, "XSCL"); esm.getHNOT(ref.mScale, "XSCL");
// TODO: support loading references from saves, there are tons of keys not recognized yet.
// The following is just an incomplete list.
if (esm.isNextSub("ACTN"))
esm.skipHSub();
if (esm.isNextSub("STPR"))
esm.skipHSub();
if (esm.isNextSub("ACDT"))
esm.skipHSub();
if (esm.isNextSub("ACSC"))
esm.skipHSub();
if (esm.isNextSub("ACSL"))
esm.skipHSub();
if (esm.isNextSub("CHRD"))
esm.skipHSub();
else if (esm.isNextSub("CRED")) // ???
esm.skipHSub();
ref.mOwner = esm.getHNOString("ANAM"); ref.mOwner = esm.getHNOString("ANAM");
ref.mGlob = esm.getHNOString("BNAM"); ref.mGlob = esm.getHNOString("BNAM");
ref.mSoul = esm.getHNOString("XSOL"); ref.mSoul = esm.getHNOString("XSOL");
@ -215,17 +304,43 @@ bool Cell::getNextRef(ESMReader &esm, CellRef &ref)
esm.getHNOT(ref.mUnam, "UNAM"); esm.getHNOT(ref.mUnam, "UNAM");
esm.getHNOT(ref.mFltv, "FLTV"); esm.getHNOT(ref.mFltv, "FLTV");
esm.getHNT(ref.mPos, "DATA", 24); esm.getHNOT(ref.mPos, "DATA", 24);
// Number of references in the cell? Maximum once in each cell, // Number of references in the cell? Maximum once in each cell,
// but not always at the beginning, and not always right. In other // but not always at the beginning, and not always right. In other
// words, completely useless. // words, completely useless.
// Update: Well, maybe not completely useless. This might actually be
// number_of_references + number_of_references_moved_here_Across_boundaries,
// and could be helpful for collecting these weird moved references.
ref.mNam0 = 0; ref.mNam0 = 0;
if (esm.isNextSub("NAM0")) if (esm.isNextSub("NAM0"))
{ {
esm.getHT(ref.mNam0); esm.getHT(ref.mNam0);
//esm.getHNOT(NAM0, "NAM0"); //esm.getHNOT(NAM0, "NAM0");
} }
if (esm.isNextSub("DELE")) {
esm.skipHSub();
ref.mDeleted = 2; // Deleted, will not respawn.
// TODO: find out when references do respawn.
} else
ref.mDeleted = 0;
return true;
}
bool Cell::getNextMVRF(ESMReader &esm, MovedCellRef &mref)
{
esm.getHT(mref.mRefnum);
esm.getHNOT(mref.mTarget, "CNDT");
// Identify references belonging to a parent file and adapt the ID accordingly.
int local = (mref.mRefnum & 0xff000000) >> 24;
size_t global = esm.getIndex() + 1;
mref.mRefnum &= 0x00ffffff; // delete old plugin ID
const ESM::ESMReader::MasterList &masters = esm.getMasters();
global = masters[local-1].index + 1;
mref.mRefnum |= global << 24; // insert global plugin ID
return true; return true;
} }

View file

@ -2,9 +2,18 @@
#define OPENMW_ESM_CELL_H #define OPENMW_ESM_CELL_H
#include <string> #include <string>
#include <vector>
#include "esmcommon.hpp" #include "esmcommon.hpp"
#include "defs.hpp" #include "defs.hpp"
#include "apps/openmw/mwbase/world.hpp"
/*
namespace MWWorld {
class ESMStore;
class CellStore;
}
*/
namespace ESM namespace ESM
{ {
@ -69,6 +78,9 @@ public:
// No idea - occurs ONCE in Morrowind.esm, for an activator // No idea - occurs ONCE in Morrowind.esm, for an activator
char mUnam; char mUnam;
// Track deleted references. 0 - not deleted, 1 - deleted, but respawns, 2 - deleted and does not respawn.
int mDeleted;
// Occurs in Tribunal.esm, eg. in the cell "Mournhold, Plaza // Occurs in Tribunal.esm, eg. in the cell "Mournhold, Plaza
// Brindisi Dorom", where it has the value 100. Also only for // Brindisi Dorom", where it has the value 100. Also only for
@ -82,6 +94,31 @@ public:
void save(ESMWriter &esm); void save(ESMWriter &esm);
}; };
/* Moved cell reference tracking object. This mainly stores the target cell
of the reference, so we can easily know where it has been moved when another
plugin tries to move it independently.
Unfortunately, we need to implement this here.
*/
class MovedCellRef
{
public:
int mRefnum;
// Target cell (if exterior)
int mTarget[2];
// TODO: Support moving references between exterior and interior cells!
// This may happen in saves, when an NPC follows the player. Tribunal
// introduces a henchman (which no one uses), so we may need this as well.
};
/// Overloaded copare operator used to search inside a list of cell refs.
bool operator==(const MovedCellRef& ref, int pRefnum);
bool operator==(const CellRef& ref, int pRefnum);
typedef std::list<MovedCellRef> MovedCellRefTracker;
typedef std::list<CellRef> CellRefTracker;
/* Cells hold data about objects, creatures, statics (rocks, walls, /* Cells hold data about objects, creatures, statics (rocks, walls,
buildings) and landscape (for exterior cells). Cells frequently buildings) and landscape (for exterior cells). Cells frequently
also has other associated LAND and PGRD records. Combined, all this also has other associated LAND and PGRD records. Combined, all this
@ -120,15 +157,24 @@ struct Cell
// Optional region name for exterior and quasi-exterior cells. // Optional region name for exterior and quasi-exterior cells.
std::string mRegion; std::string mRegion;
ESM_Context mContext; // File position std::vector<ESM_Context> mContextList; // File position; multiple positions for multiple plugin support
DATAstruct mData; DATAstruct mData;
AMBIstruct mAmbi; AMBIstruct mAmbi;
float mWater; // Water level float mWater; // Water level
bool mWaterInt; bool mWaterInt;
int mMapColor; int mMapColor;
int mNAM0; int mNAM0;
void load(ESMReader &esm); // References "leased" from another cell (i.e. a different cell
// introduced this ref, and it has been moved here by a plugin)
CellRefTracker mLeasedRefs;
MovedCellRefTracker mMovedRefs;
void load(ESMReader &esm, MWWorld::ESMStore &store);
// 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) {};
void save(ESMWriter &esm); void save(ESMWriter &esm);
bool isExterior() const bool isExterior() const
@ -151,7 +197,7 @@ struct Cell
// somewhere other than the file system, you need to pre-open the // somewhere other than the file system, you need to pre-open the
// ESMReader, and the filename must match the stored filename // ESMReader, and the filename must match the stored filename
// exactly. // exactly.
void restore(ESMReader &esm) const; void restore(ESMReader &esm, int iCtx) const;
std::string getDescription() const; std::string getDescription() const;
///< Return a short string describing the cell (mostly used for debugging/logging purpose) ///< Return a short string describing the cell (mostly used for debugging/logging purpose)
@ -163,6 +209,11 @@ struct Cell
reuse one memory location without blanking it between calls. reuse one memory location without blanking it between calls.
*/ */
static bool getNextRef(ESMReader &esm, CellRef &ref); static bool getNextRef(ESMReader &esm, CellRef &ref);
/* This fetches an MVRF record, which is used to track moved references.
* Since they are comparably rare, we use a separate method for this.
*/
static bool getNextMVRF(ESMReader &esm, MovedCellRef &mref);
}; };
} }
#endif #endif

View file

@ -76,8 +76,29 @@ std::string GameSetting::getString() const
{ {
if (mType==VT_String) if (mType==VT_String)
return mStr; return mStr;
throw std::runtime_error ("GMST " + mId + " is not a string"); throw std::runtime_error ("GMST " + mId + " is not a string");
} }
void GameSetting::blank()
{
mStr.clear();
mI = 0;
mF = 0;
mType = VT_Float;
}
bool operator== (const GameSetting& left, const GameSetting& right)
{
if (left.mType!=right.mType)
return false;
switch (left.mType)
{
case VT_Float: return left.mF==right.mF;
case VT_Int: return left.mI==right.mI;
case VT_String: return left.mStr==right.mStr;
default: return false;
}
}
} }

View file

@ -26,17 +26,22 @@ struct GameSetting
VarType mType; VarType mType;
void load(ESMReader &esm); void load(ESMReader &esm);
int getInt() const; int getInt() const;
///< Throws an exception if GMST is not of type int or float. ///< Throws an exception if GMST is not of type int or float.
float getFloat() const; float getFloat() const;
///< Throws an exception if GMST is not of type int or float. ///< Throws an exception if GMST is not of type int or float.
std::string getString() const; std::string getString() const;
///< Throwns an exception if GMST is not of type string. ///< Throwns an exception if GMST is not of type string.
void save(ESMWriter &esm); void save(ESMWriter &esm);
void blank();
///< Set record to default state (does not touch the ID).
}; };
bool operator== (const GameSetting& left, const GameSetting& right);
} }
#endif #endif

View file

@ -81,6 +81,7 @@ Land::~Land()
void Land::load(ESMReader &esm) void Land::load(ESMReader &esm)
{ {
mEsm = &esm; mEsm = &esm;
mPlugin = mEsm->getIndex();
// Get the grid location // Get the grid location
esm.getSubNameIs("INTV"); esm.getSubNameIs("INTV");

View file

@ -23,6 +23,7 @@ struct Land
int mFlags; // Only first four bits seem to be used, don't know what int mFlags; // Only first four bits seem to be used, don't know what
// they mean. // they mean.
int mX, mY; // Map coordinates. int mX, mY; // Map coordinates.
int mPlugin; // Plugin index, used to reference the correct material palette.
// File context. This allows the ESM reader to be 'reset' to this // File context. This allows the ESM reader to be 'reset' to this
// location later when we are ready to load the full data set. // location later when we are ready to load the full data set.

View file

@ -0,0 +1,351 @@
#include <QtGui>
#include <components/esm/esmreader.hpp>
#include <components/files/configurationmanager.hpp>
#include "model/datafilesmodel.hpp"
#include "model/esm/esmfile.hpp"
#include "utils/filedialog.hpp"
#include "utils/lineedit.hpp"
#include "utils/naturalsort.hpp"
#include "datafileslist.hpp"
#include <boost/version.hpp>
/**
* Workaround for problems with whitespaces in paths in older versions of Boost library
*/
#if (BOOST_VERSION <= 104600)
namespace boost
{
template<>
inline boost::filesystem::path lexical_cast<boost::filesystem::path, std::string>(const std::string& arg)
{
return boost::filesystem::path(arg);
}
} /* namespace boost */
#endif /* (BOOST_VERSION <= 104600) */
using namespace ESM;
using namespace std;
//sort QModelIndexList ascending
bool rowGreaterThan(const QModelIndex &index1, const QModelIndex &index2)
{
return index1.row() >= index2.row();
}
//sort QModelIndexList descending
bool rowSmallerThan(const QModelIndex &index1, const QModelIndex &index2)
{
return index1.row() <= index2.row();
}
DataFilesList::DataFilesList(Files::ConfigurationManager &cfg, QWidget *parent)
: QWidget(parent)
, mCfgMgr(cfg)
{
// Models
mMastersModel = new DataFilesModel(this);
mPluginsModel = new DataFilesModel(this);
mPluginsProxyModel = new QSortFilterProxyModel();
mPluginsProxyModel->setDynamicSortFilter(true);
mPluginsProxyModel->setSourceModel(mPluginsModel);
// Filter toolbar
QLabel *filterLabel = new QLabel(tr("&Filter:"), this);
LineEdit *filterLineEdit = new LineEdit(this);
filterLabel->setBuddy(filterLineEdit);
QToolBar *filterToolBar = new QToolBar(this);
filterToolBar->setMovable(false);
// Create a container widget and a layout to get the spacer to work
QWidget *filterWidget = new QWidget(this);
QHBoxLayout *filterLayout = new QHBoxLayout(filterWidget);
QSpacerItem *hSpacer1 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
filterLayout->addItem(hSpacer1);
filterLayout->addWidget(filterLabel);
filterLayout->addWidget(filterLineEdit);
filterToolBar->addWidget(filterWidget);
QCheckBox checkBox;
unsigned int height = checkBox.sizeHint().height() + 4;
mMastersTable = new QTableView(this);
mMastersTable->setModel(mMastersModel);
mMastersTable->setObjectName("MastersTable");
mMastersTable->setSelectionBehavior(QAbstractItemView::SelectRows);
mMastersTable->setSelectionMode(QAbstractItemView::SingleSelection);
mMastersTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
mMastersTable->setAlternatingRowColors(true);
mMastersTable->horizontalHeader()->setStretchLastSection(true);
mMastersTable->horizontalHeader()->hide();
// Set the row height to the size of the checkboxes
mMastersTable->verticalHeader()->setDefaultSectionSize(height);
mMastersTable->verticalHeader()->setResizeMode(QHeaderView::Fixed);
mMastersTable->verticalHeader()->hide();
mMastersTable->setColumnHidden(1, true);
mMastersTable->setColumnHidden(2, true);
mMastersTable->setColumnHidden(3, true);
mMastersTable->setColumnHidden(4, true);
mMastersTable->setColumnHidden(5, true);
mMastersTable->setColumnHidden(6, true);
mMastersTable->setColumnHidden(7, true);
mMastersTable->setColumnHidden(8, true);
mPluginsTable = new QTableView(this);
mPluginsTable->setModel(mPluginsProxyModel);
mPluginsTable->setObjectName("PluginsTable");
mPluginsTable->setContextMenuPolicy(Qt::CustomContextMenu);
mPluginsTable->setSelectionBehavior(QAbstractItemView::SelectRows);
mPluginsTable->setSelectionMode(QAbstractItemView::SingleSelection);
mPluginsTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
mPluginsTable->setAlternatingRowColors(true);
mPluginsTable->setVerticalScrollMode(QAbstractItemView::ScrollPerItem);
mPluginsTable->horizontalHeader()->setStretchLastSection(true);
mPluginsTable->horizontalHeader()->hide();
mPluginsTable->verticalHeader()->setDefaultSectionSize(height);
mPluginsTable->verticalHeader()->setResizeMode(QHeaderView::Fixed);
mPluginsTable->setColumnHidden(1, true);
mPluginsTable->setColumnHidden(2, true);
mPluginsTable->setColumnHidden(3, true);
mPluginsTable->setColumnHidden(4, true);
mPluginsTable->setColumnHidden(5, true);
mPluginsTable->setColumnHidden(6, true);
mPluginsTable->setColumnHidden(7, true);
mPluginsTable->setColumnHidden(8, true);
// Add both tables to a splitter
QSplitter *splitter = new QSplitter(this);
splitter->setOrientation(Qt::Horizontal);
splitter->addWidget(mMastersTable);
splitter->addWidget(mPluginsTable);
// Adjust the default widget widths inside the splitter
QList<int> sizeList;
sizeList << 175 << 200;
splitter->setSizes(sizeList);
QVBoxLayout *pageLayout = new QVBoxLayout(this);
pageLayout->addWidget(filterToolBar);
pageLayout->addWidget(splitter);
connect(mPluginsTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(setCheckState(QModelIndex)));
connect(mMastersTable, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(setCheckState(QModelIndex)));
connect(mMastersModel, SIGNAL(checkedItemsChanged(QStringList,QStringList)), mPluginsModel, SLOT(slotcheckedItemsChanged(QStringList,QStringList)));
connect(filterLineEdit, SIGNAL(textChanged(QString)), this, SLOT(filterChanged(QString)));
connect(mPluginsTable, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint)));
createActions();
}
void DataFilesList::createActions()
{
// Refresh the plugins
QAction *refreshAction = new QAction(tr("Refresh"), this);
refreshAction->setShortcut(QKeySequence(tr("F5")));
connect(refreshAction, SIGNAL(triggered()), this, SLOT(refresh()));
// Context menu actions
mCheckAction = new QAction(tr("Check selected"), this);
connect(mCheckAction, SIGNAL(triggered()), this, SLOT(check()));
mUncheckAction = new QAction(tr("Uncheck selected"), this);
connect(mUncheckAction, SIGNAL(triggered()), this, SLOT(uncheck()));
// Context menu for the plugins table
mContextMenu = new QMenu(this);
mContextMenu->addAction(mCheckAction);
mContextMenu->addAction(mUncheckAction);
}
bool DataFilesList::setupDataFiles(Files::PathContainer dataDirs, const QString encoding)
{
// Set the charset for reading the esm/esp files
if (!encoding.isEmpty() && encoding != QLatin1String("win1252")) {
mMastersModel->setEncoding(encoding);
mPluginsModel->setEncoding(encoding);
}
// Add the paths to the respective models
for (Files::PathContainer::iterator it = dataDirs.begin(); it != dataDirs.end(); ++it) {
QString path = QString::fromStdString(it->string());
path.remove(QChar('\"'));
mMastersModel->addMasters(path);
mPluginsModel->addPlugins(path);
}
mMastersModel->sort(0);
mPluginsModel->sort(0);
// mMastersTable->sortByColumn(3, Qt::AscendingOrder);
// mPluginsTable->sortByColumn(3, Qt::AscendingOrder);
return true;
}
void DataFilesList::selectedFiles(std::vector<boost::filesystem::path>& paths)
{
QStringList masterPaths = mMastersModel->checkedItemsPaths();
foreach (const QString &path, masterPaths)
{
paths.push_back(path.toStdString());
}
QStringList pluginPaths = mPluginsModel->checkedItemsPaths();
foreach (const QString &path, pluginPaths)
{
paths.push_back(path.toStdString());
}
}
void DataFilesList::check()
{
// Check the current selection
if (!mPluginsTable->selectionModel()->hasSelection()) {
return;
}
QModelIndexList indexes = mPluginsTable->selectionModel()->selectedIndexes();
//sort selection ascending because selectedIndexes returns an unsorted list
//qSort(indexes.begin(), indexes.end(), rowSmallerThan);
foreach (const QModelIndex &index, indexes) {
if (!index.isValid())
return;
mPluginsModel->setCheckState(index, Qt::Checked);
}
}
void DataFilesList::uncheck()
{
// uncheck the current selection
if (!mPluginsTable->selectionModel()->hasSelection()) {
return;
}
QModelIndexList indexes = mPluginsTable->selectionModel()->selectedIndexes();
//sort selection ascending because selectedIndexes returns an unsorted list
//qSort(indexes.begin(), indexes.end(), rowSmallerThan);
foreach (const QModelIndex &index, indexes) {
if (!index.isValid())
return;
mPluginsModel->setCheckState(index, Qt::Unchecked);
}
}
void DataFilesList::refresh()
{
mPluginsModel->sort(0);
// Refresh the plugins table
mPluginsTable->scrollToTop();
}
void DataFilesList::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(index);
(mPluginsModel->checkState(sourceIndex) == Qt::Checked)
? mPluginsModel->setCheckState(sourceIndex, Qt::Unchecked)
: mPluginsModel->setCheckState(sourceIndex, Qt::Checked);
}
if (object->objectName() == QLatin1String("MastersTable")) {
(mMastersModel->checkState(index) == Qt::Checked)
? mMastersModel->setCheckState(index, Qt::Unchecked)
: mMastersModel->setCheckState(index, Qt::Checked);
}
return;
}
void DataFilesList::uncheckAll()
{
mMastersModel->uncheckAll();
mPluginsModel->uncheckAll();
}
void DataFilesList::filterChanged(const QString filter)
{
QRegExp regExp(filter, Qt::CaseInsensitive, QRegExp::FixedString);
mPluginsProxyModel->setFilterRegExp(regExp);
}
void DataFilesList::showContextMenu(const QPoint &point)
{
// Make sure there are plugins in the view
if (!mPluginsTable->selectionModel()->hasSelection()) {
return;
}
QPoint globalPos = mPluginsTable->mapToGlobal(point);
QModelIndexList indexes = mPluginsTable->selectionModel()->selectedIndexes();
// Show the check/uncheck actions depending on the state of the selected items
mUncheckAction->setEnabled(false);
mCheckAction->setEnabled(false);
foreach (const QModelIndex &index, indexes) {
if (!index.isValid())
return;
(mPluginsModel->checkState(index) == Qt::Checked)
? mUncheckAction->setEnabled(true)
: mCheckAction->setEnabled(true);
}
// Show menu
mContextMenu->exec(globalPos);
}
void DataFilesList::setCheckState(const QString& element, Qt::CheckState state)
{
EsmFile *file = mPluginsModel->findItem(element);
if (file)
{
mPluginsModel->setCheckState(mPluginsModel->indexFromItem(file), Qt::Checked);
}
else
{
file = mMastersModel->findItem(element);
mMastersModel->setCheckState(mMastersModel->indexFromItem(file), Qt::Checked);
}
}
QStringList DataFilesList::checkedFiles()
{
return mMastersModel->checkedItems() + mPluginsModel->checkedItems();
}

View file

@ -0,0 +1,77 @@
#ifndef DATAFILESLIST_H
#define DATAFILESLIST_H
#include <QWidget>
#include <QModelIndex>
#include <components/files/collections.hpp>
class QTableView;
class QSortFilterProxyModel;
class QSettings;
class QAction;
class QToolBar;
class QMenu;
class ProfilesComboBox;
class DataFilesModel;
class TextInputDialog;
namespace Files { struct ConfigurationManager; }
class DataFilesList : public QWidget
{
Q_OBJECT
public:
DataFilesList(Files::ConfigurationManager& cfg, QWidget *parent = 0);
bool setupDataFiles(Files::PathContainer dataDirs, const QString encoding);
void selectedFiles(std::vector<boost::filesystem::path>& paths);
void uncheckAll();
QStringList checkedFiles();
void setCheckState(const QString& element, Qt::CheckState);
public slots:
void setCheckState(QModelIndex index);
void filterChanged(const QString filter);
void showContextMenu(const QPoint &point);
// Action slots
// void moveUp();
// void moveDown();
// void moveTop();
// void moveBottom();
void check();
void uncheck();
void refresh();
private:
DataFilesModel *mMastersModel;
DataFilesModel *mPluginsModel;
QSortFilterProxyModel *mPluginsProxyModel;
QTableView *mMastersTable;
QTableView *mPluginsTable;
QMenu *mContextMenu;
// QAction *mMoveUpAction;
// QAction *mMoveDownAction;
// QAction *mMoveTopAction;
// QAction *mMoveBottomAction;
QAction *mCheckAction;
QAction *mUncheckAction;
Files::ConfigurationManager &mCfgMgr;
// const QStringList checkedPlugins();
// const QStringList selectedMasters();
void createActions();
};
#endif

View file

@ -292,6 +292,7 @@ void DataFilesModel::addMasters(const QString &path)
EsmFile *file = new EsmFile(master); EsmFile *file = new EsmFile(master);
file->setDates(info.lastModified(), info.lastRead()); file->setDates(info.lastModified(), info.lastRead());
file->setPath(info.absoluteFilePath());
// Add the master to the table // Add the master to the table
if (findItem(master) == 0) if (findItem(master) == 0)
@ -427,6 +428,25 @@ QStringList DataFilesModel::checkedItems()
return list; return list;
} }
QStringList DataFilesModel::checkedItemsPaths()
{
QStringList list;
QList<EsmFile *>::ConstIterator it;
QList<EsmFile *>::ConstIterator itEnd = mFiles.constEnd();
int i = 0;
for (it = mFiles.constBegin(); it != itEnd; ++it) {
EsmFile *file = item(i);
++i;
if (mCheckStates[file->fileName()] == Qt::Checked && mAvailableFiles.contains(file->fileName()))
list << file->path();
}
return list;
}
void DataFilesModel::uncheckAll() void DataFilesModel::uncheckAll()
{ {
emit layoutAboutToBeChanged(); emit layoutAboutToBeChanged();

View file

@ -43,6 +43,7 @@ public:
QStringList checkedItems(); QStringList checkedItems();
QStringList uncheckedItems(); QStringList uncheckedItems();
QStringList checkedItemsPaths();
Qt::CheckState checkState(const QModelIndex &index); Qt::CheckState checkState(const QModelIndex &index);
void setCheckState(const QModelIndex &index, Qt::CheckState state); void setCheckState(const QModelIndex &index, Qt::CheckState state);