diff --git a/CMakeLists.txt b/CMakeLists.txt index 954e161a1..fb3827588 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -73,6 +73,8 @@ option(OGRE_STATIC "Link static build of Ogre and Ogre Plugins into the binarie option(BOOST_STATIC "Link static build of Boost into the binaries" FALSE) option(SDL2_STATIC "Link static build of SDL into the binaries" FALSE) +option(OPENMW_UNITY_BUILD "Use fewer compilation units to speed up compile time" FALSE) + # Apps and tools option(BUILD_BSATOOL "build BSA extractor" OFF) option(BUILD_ESMTOOL "build ESM inspector" ON) @@ -463,7 +465,6 @@ if(WIN32) INSTALL(FILES "${OpenMW_SOURCE_DIR}/readme.txt" "${OpenMW_SOURCE_DIR}/GPL3.txt" - "${OpenMW_SOURCE_DIR}/OFL.txt" "${OpenMW_SOURCE_DIR}/DejaVu Font License.txt" "${OpenMW_BINARY_DIR}/settings-default.cfg" "${OpenMW_BINARY_DIR}/transparency-overrides.cfg" diff --git a/OFL.txt b/OFL.txt deleted file mode 100644 index 043e85e83..000000000 --- a/OFL.txt +++ /dev/null @@ -1,93 +0,0 @@ -Copyright (c) 2010, 2011 Georg Duffner (http://www.georgduffner.at) - -This Font Software is licensed under the SIL Open Font License, Version 1.1. -This license is copied below, and is also available with a FAQ at: -http://scripts.sil.org/OFL - - ------------------------------------------------------------ -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ------------------------------------------------------------ - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide -development of collaborative font projects, to support the font creation -efforts of academic and linguistic communities, and to provide a free and -open framework in which fonts may be shared and improved in partnership -with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply -to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. This may -include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software components as -distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting -- in part or in whole -- any of the components of the -Original Version, by changing formats or by porting the Font Software to a -new environment. - -"Author" refers to any designer, engineer, programmer, technical -writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Font Software, to use, study, copy, merge, embed, modify, -redistribute, and sell modified and unmodified copies of the Font -Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, -in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, -redistributed and/or sold with any software, provided that each copy -contains the above copyright notice and this license. These can be -included either as stand-alone text files, human-readable headers or -in the appropriate machine-readable metadata fields within text or -binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font -Name(s) unless explicit written permission is granted by the corresponding -Copyright Holder. This restriction only applies to the primary font name as -presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font -Software shall not be used to promote, endorse or advertise any -Modified Version, except to acknowledge the contribution(s) of the -Copyright Holder(s) and the Author(s) or with their explicit written -permission. - -5) The Font Software, modified or unmodified, in part or in whole, -must be distributed entirely under this license, and must not be -distributed under any other license. The requirement for fonts to -remain under this license does not apply to any document created -using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 7cdaad5be..b2b534031 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -60,7 +60,7 @@ opencs_hdrs_noqt (view/doc opencs_units (view/world table tablesubview scriptsubview util regionmapsubview tablebottombox creator genericcreator cellcreator referenceablecreator referencecreator scenesubview scenetoolbar scenetool - scenetoolmode infocreator scriptedit previewsubview + scenetoolmode infocreator scriptedit dialoguesubview previewsubview ) opencs_units (view/render @@ -73,8 +73,7 @@ opencs_units_noqt (view/render ) opencs_units_noqt (view/world - dialoguesubview subviews - enumdelegate vartypedelegate recordstatusdelegate idtypedelegate datadisplaydelegate + subviews enumdelegate vartypedelegate recordstatusdelegate idtypedelegate datadisplaydelegate scripthighlighter idvalidator dialoguecreator ) diff --git a/apps/opencs/model/world/columnbase.cpp b/apps/opencs/model/world/columnbase.cpp index 34bad20cc..f6363fe2e 100644 --- a/apps/opencs/model/world/columnbase.cpp +++ b/apps/opencs/model/world/columnbase.cpp @@ -17,4 +17,9 @@ bool CSMWorld::ColumnBase::isUserEditable() const std::string CSMWorld::ColumnBase::getTitle() const { return Columns::getName (static_cast (mColumnId)); +} + +int CSMWorld::ColumnBase::getId() const +{ + return mColumnId; } \ No newline at end of file diff --git a/apps/opencs/model/world/columnbase.hpp b/apps/opencs/model/world/columnbase.hpp index e04333608..fe310d0aa 100644 --- a/apps/opencs/model/world/columnbase.hpp +++ b/apps/opencs/model/world/columnbase.hpp @@ -28,6 +28,7 @@ namespace CSMWorld { Display_None, //Do not use Display_String, + Display_LongString, //CONCRETE TYPES STARTS HERE Display_Skill, @@ -105,6 +106,8 @@ namespace CSMWorld ///< Can this column be edited directly by the user? virtual std::string getTitle() const; + + virtual int getId() const; }; template diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index b1c61b76a..89fb586aa 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -201,7 +201,7 @@ namespace CSMWorld struct DescriptionColumn : public Column { DescriptionColumn() - : Column (Columns::ColumnId_Description, ColumnBase::Display_String) + : Column (Columns::ColumnId_Description, ColumnBase::Display_LongString) {} virtual QVariant get (const Record& record) const @@ -833,7 +833,7 @@ namespace CSMWorld virtual bool isUserEditable() const { - return false; + return true; } }; @@ -1113,7 +1113,7 @@ namespace CSMWorld virtual bool isUserEditable() const { - return false; + return true; } }; @@ -1379,7 +1379,7 @@ namespace CSMWorld template struct QuestDescriptionColumn : public Column { - QuestDescriptionColumn() : Column (Columns::ColumnId_QuestDescription, ColumnBase::Display_String) {} + QuestDescriptionColumn() : Column (Columns::ColumnId_QuestDescription, ColumnBase::Display_LongString) {} virtual QVariant get (const Record& record) const { @@ -1559,7 +1559,7 @@ namespace CSMWorld template struct ResponseColumn : public Column { - ResponseColumn() : Column (Columns::ColumnId_Response, ColumnBase::Display_String) {} + ResponseColumn() : Column (Columns::ColumnId_Response, ColumnBase::Display_LongString) {} virtual QVariant get (const Record& record) const { diff --git a/apps/opencs/model/world/idtable.cpp b/apps/opencs/model/world/idtable.cpp index 56b16f5a1..50998c36f 100644 --- a/apps/opencs/model/world/idtable.cpp +++ b/apps/opencs/model/world/idtable.cpp @@ -235,4 +235,9 @@ std::pair CSMWorld::IdTable::view (int row) id = "sys::default"; return std::make_pair (UniversalId (UniversalId::Type_Scene, id), hint); +} + +int CSMWorld::IdTable::getColumnId(int column) const +{ + return mIdCollection->getColumn(column).getId(); } \ No newline at end of file diff --git a/apps/opencs/model/world/idtable.hpp b/apps/opencs/model/world/idtable.hpp index 7d812b083..8b5462825 100644 --- a/apps/opencs/model/world/idtable.hpp +++ b/apps/opencs/model/world/idtable.hpp @@ -106,6 +106,8 @@ namespace CSMWorld std::pair view (int row) const; ///< Return the UniversalId and the hint for viewing \a row. If viewing is not /// supported by this table, return (UniversalId::Type_None, ""). + + int getColumnId(int column) const; }; } diff --git a/apps/opencs/model/world/tablemimedata.cpp b/apps/opencs/model/world/tablemimedata.cpp index b56c9c8c2..9dcecf3bc 100644 --- a/apps/opencs/model/world/tablemimedata.cpp +++ b/apps/opencs/model/world/tablemimedata.cpp @@ -443,4 +443,9 @@ CSMWorld::ColumnBase::Display CSMWorld::TableMimeData::convertEnums (CSMWorld::U default: return CSMWorld::ColumnBase::Display_None; } +} + +const CSMDoc::Document* CSMWorld::TableMimeData::getDocumentPtr() const +{ + return &mDocument; } \ No newline at end of file diff --git a/apps/opencs/model/world/tablemimedata.hpp b/apps/opencs/model/world/tablemimedata.hpp index 7687f3555..44ac0f5f6 100644 --- a/apps/opencs/model/world/tablemimedata.hpp +++ b/apps/opencs/model/world/tablemimedata.hpp @@ -48,6 +48,8 @@ namespace CSMWorld UniversalId returnMatching(UniversalId::Type type) const; + const CSMDoc::Document* getDocumentPtr() const; + UniversalId returnMatching(CSMWorld::ColumnBase::Display type) const; static CSMWorld::UniversalId::Type convertEnums(CSMWorld::ColumnBase::Display type); diff --git a/apps/opencs/view/doc/subview.cpp b/apps/opencs/view/doc/subview.cpp index 6160f2673..dcd85945f 100644 --- a/apps/opencs/view/doc/subview.cpp +++ b/apps/opencs/view/doc/subview.cpp @@ -18,4 +18,10 @@ void CSVDoc::SubView::updateEditorSetting (const QString &settingName, const QSt void CSVDoc::SubView::setStatusBar (bool show) {} -void CSVDoc::SubView::useHint (const std::string& hint) {} \ No newline at end of file +void CSVDoc::SubView::useHint (const std::string& hint) {} + +void CSVDoc::SubView::setUniversalId (const CSMWorld::UniversalId& id) +{ + mUniversalId = id; + setWindowTitle (mUniversalId.toString().c_str()); +} \ No newline at end of file diff --git a/apps/opencs/view/doc/subview.hpp b/apps/opencs/view/doc/subview.hpp index 59781f869..85274a18d 100644 --- a/apps/opencs/view/doc/subview.hpp +++ b/apps/opencs/view/doc/subview.hpp @@ -27,6 +27,8 @@ namespace CSVDoc // not implemented SubView (const SubView&); SubView& operator= (SubView&); + protected: + void setUniversalId(const CSMWorld::UniversalId& id); public: diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index de3f476af..bc34c6118 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -316,8 +316,16 @@ void CSVDoc::View::addSubView (const CSMWorld::UniversalId& id, const std::strin /// \todo add an user setting to reuse sub views (on a per document basis or on a per top level view basis) - SubView *view = mSubViewFactory.makeSubView (id, *mDocument); - + const std::vector referenceables(CSMWorld::UniversalId::listReferenceableTypes()); + SubView *view = NULL; + if(std::find(referenceables.begin(), referenceables.end(), id.getType()) != referenceables.end()) + { + view = mSubViewFactory.makeSubView (CSMWorld::UniversalId(CSMWorld::UniversalId::Type_Referenceable, id.getId()), *mDocument); + } else + { + view = mSubViewFactory.makeSubView (id, *mDocument); + } + assert(view); if (!hint.empty()) view->useHint (hint); diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index cedb20de9..ae7c5923b 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -1,98 +1,663 @@ #include "dialoguesubview.hpp" +#include +#include + #include #include +#include #include #include #include #include +#include #include +#include +#include +#include +#include +#include +#include +#include #include "../../model/world/columnbase.hpp" #include "../../model/world/idtable.hpp" +#include "../../model/world/columns.hpp" +#include "../../model/world/record.hpp" +#include "../../model/world/tablemimedata.hpp" +#include "../../model/doc/document.hpp" +#include "../../model/world/commands.hpp" -CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, - bool createAndDelete) -: SubView (id) +#include "recordstatusdelegate.hpp" +#include "util.hpp" +#include "tablebottombox.hpp" +/* +==============================NotEditableSubDelegate========================================== +*/ +CSVWorld::NotEditableSubDelegate::NotEditableSubDelegate(const CSMWorld::IdTable* table, QObject * parent) : +QAbstractItemDelegate(parent), +mTable(table) +{} + +void CSVWorld::NotEditableSubDelegate::setEditorData (QLabel* editor, const QModelIndex& index) const { - QWidget *widget = new QWidget (this); - - setWidget (widget); - - QGridLayout *layout = new QGridLayout; - - widget->setLayout (layout); - - QAbstractItemModel *model = document.getData().getTableModel (id); - - int columns = model->columnCount(); - - mWidgetMapper = new QDataWidgetMapper (this); - mWidgetMapper->setModel (model); - - for (int i=0; iheaderData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Flags).toInt(); - - if (flags & CSMWorld::ColumnBase::Flag_Dialogue) + v = index.data(Qt::DisplayRole); + if (!v.isValid()) { - layout->addWidget (new QLabel (model->headerData (i, Qt::Horizontal).toString()), i, 0); - - CSMWorld::ColumnBase::Display display = static_cast - (model->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); - - QWidget *widget = 0; - - if (model->flags (model->index (0, i)) & Qt::ItemIsEditable) - { - switch (display) - { - case CSMWorld::ColumnBase::Display_String: - - layout->addWidget (widget = new QLineEdit, i, 1); - break; - - case CSMWorld::ColumnBase::Display_Integer: - - /// \todo configure widget properly (range) - layout->addWidget (widget = new QSpinBox, i, 1); - break; - - case CSMWorld::ColumnBase::Display_Float: - - /// \todo configure widget properly (range, format?) - layout->addWidget (widget = new QDoubleSpinBox, i, 1); - break; - - default: break; // silence warnings for other times for now - } - } - else - { - switch (display) - { - case CSMWorld::ColumnBase::Display_String: - case CSMWorld::ColumnBase::Display_Integer: - case CSMWorld::ColumnBase::Display_Float: - - layout->addWidget (widget = new QLabel, i, 1); - break; - - default: break; // silence warnings for other times for now - } - } - - if (widget) - mWidgetMapper->addMapping (widget, i); + return; } } - mWidgetMapper->setCurrentModelIndex ( - dynamic_cast (*model).getModelIndex (id.getId(), 0)); + if (QVariant::String == v.type()) + { + editor->setText(v.toString()); + } else //else we are facing enums + { + int data = v.toInt(); + std::vector enumNames (CSMWorld::Columns::getEnums (static_cast (mTable->getColumnId (index.column())))); + editor->setText(QString::fromUtf8(enumNames.at(data).c_str())); + } +} + +void CSVWorld::NotEditableSubDelegate::setModelData (QWidget* editor, QAbstractItemModel* model, const QModelIndex& index, CSMWorld::ColumnBase::Display display) const +{ + //not editable widgets will not save model data +} + +void CSVWorld::NotEditableSubDelegate::paint (QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + //does nothing +} + +QSize CSVWorld::NotEditableSubDelegate::sizeHint (const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + return QSize(); +} + +QWidget* CSVWorld::NotEditableSubDelegate::createEditor (QWidget *parent, + const QStyleOptionViewItem& option, + const QModelIndex& index, + CSMWorld::ColumnBase::Display display) const +{ + return new QLabel(parent); +} + +/* +==============================DialogueDelegateDispatcherProxy========================================== +*/ +CSVWorld::DialogueDelegateDispatcherProxy::refWrapper::refWrapper(const QModelIndex& index) : +mIndex(index) +{} + +CSVWorld::DialogueDelegateDispatcherProxy::DialogueDelegateDispatcherProxy(QWidget* editor, CSMWorld::ColumnBase::Display display) : +mEditor(editor), +mDisplay(display), +mIndexWrapper(NULL) +{ +} + +void CSVWorld::DialogueDelegateDispatcherProxy::editorDataCommited() +{ + if (mIndexWrapper.get()) + { + emit editorDataCommited(mEditor, mIndexWrapper->mIndex, mDisplay); + } +} + +void CSVWorld::DialogueDelegateDispatcherProxy::setIndex(const QModelIndex& index) +{ + mIndexWrapper.reset(new refWrapper(index)); +} + +QWidget* CSVWorld::DialogueDelegateDispatcherProxy::getEditor() const +{ + return mEditor; +} + +void CSVWorld::DialogueDelegateDispatcherProxy::tableMimeDataDropped(const std::vector& data, const CSMDoc::Document* document) +{ + QLineEdit* lineEdit = qobject_cast(mEditor); + { + if (!lineEdit or !mIndexWrapper.get()) + { + return; + } + } + for (unsigned i = 0; i < data.size(); ++i) + { + if (mDisplay == CSMWorld::TableMimeData::convertEnums(data[i].getType())) + { + emit tableMimeDataDropped(mEditor, mIndexWrapper->mIndex, data[i], document); + emit editorDataCommited(mEditor, mIndexWrapper->mIndex, mDisplay); + break; + } + } +} +/* +==============================DialogueDelegateDispatcher========================================== +*/ + +CSVWorld::DialogueDelegateDispatcher::DialogueDelegateDispatcher(QObject* parent, CSMWorld::IdTable* table, QUndoStack& undoStack) : +mParent(parent), +mTable(table), +mUndoStack(undoStack), +mNotEditableDelegate(table, parent) +{ +} + +CSVWorld::CommandDelegate* CSVWorld::DialogueDelegateDispatcher::makeDelegate(CSMWorld::ColumnBase::Display display) +{ + CommandDelegate *delegate = NULL; + std::map::const_iterator delegateIt(mDelegates.find(display)); + if (delegateIt == mDelegates.end()) + { + delegate = CommandDelegateFactoryCollection::get().makeDelegate ( + display, mUndoStack, mParent); + mDelegates.insert(std::make_pair(display, delegate)); + } else + { + delegate = delegateIt->second; + } + return delegate; +} + +void CSVWorld::DialogueDelegateDispatcher::editorDataCommited(QWidget* editor, const QModelIndex& index, CSMWorld::ColumnBase::Display display) +{ + setModelData(editor, mTable, index, display); +} + +void CSVWorld::DialogueDelegateDispatcher::setEditorData (QWidget* editor, const QModelIndex& index) const +{ + CSMWorld::ColumnBase::Display display = static_cast + (mTable->headerData (index.column(), Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); + + QLabel* label = qobject_cast(editor); + if(label) + { + mNotEditableDelegate.setEditorData(label, index); + return; + } + + std::map::const_iterator delegateIt(mDelegates.find(display)); + if (delegateIt != mDelegates.end()) + { + delegateIt->second->setEditorData(editor, index, true); + } + + for (unsigned i = 0; i < mProxys.size(); ++i) + { + if (mProxys[i]->getEditor() == editor) + { + mProxys[i]->setIndex(index); + } + } +} + +void CSVWorld::DialogueDelegateDispatcher::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index, CSMWorld::ColumnBase::Display display) const +{ + std::map::const_iterator delegateIt(mDelegates.find(display)); + if (delegateIt != mDelegates.end()) + { + delegateIt->second->setModelData(editor, model, index); + } +} + +void CSVWorld::DialogueDelegateDispatcher::paint (QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + //Does nothing +} + +QSize CSVWorld::DialogueDelegateDispatcher::sizeHint (const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + return QSize(); //silencing warning, otherwise does nothing +} + +QWidget* CSVWorld::DialogueDelegateDispatcher::makeEditor(CSMWorld::ColumnBase::Display display, const QModelIndex& index) +{ + QVariant variant = index.data(); + if (!variant.isValid()) + { + variant = index.data(Qt::DisplayRole); + if (!variant.isValid()) + { + return NULL; + } + } + + QWidget* editor = NULL; + if (! (mTable->flags (index) & Qt::ItemIsEditable)) + { + return mNotEditableDelegate.createEditor(qobject_cast(mParent), QStyleOptionViewItem(), index, display); + } + + std::map::iterator delegateIt(mDelegates.find(display)); + if (delegateIt != mDelegates.end()) + { + editor = delegateIt->second->createEditor(qobject_cast(mParent), QStyleOptionViewItem(), index, display); + DialogueDelegateDispatcherProxy* proxy = new DialogueDelegateDispatcherProxy(editor, display); + + bool skip = false; + if (qobject_cast(editor)) + { + connect(editor, SIGNAL(editingFinished()), proxy, SLOT(editorDataCommited())); + connect(editor, SIGNAL(tableMimeDataDropped(const std::vector&, const CSMDoc::Document*)), + proxy, SLOT(tableMimeDataDropped(const std::vector&, const CSMDoc::Document*))); + connect(proxy, SIGNAL(tableMimeDataDropped(QWidget*, const QModelIndex&, const CSMWorld::UniversalId&, const CSMDoc::Document*)), + this, SIGNAL(tableMimeDataDropped(QWidget*, const QModelIndex&, const CSMWorld::UniversalId&, const CSMDoc::Document*))); + skip = true; + } + if(!skip && qobject_cast(editor)) + { + connect(editor, SIGNAL(stateChanged(int)), proxy, SLOT(editorDataCommited())); + skip = true; + } + if(!skip && qobject_cast(editor)) + { + connect(editor, SIGNAL(textChanged()), proxy, SLOT(editorDataCommited())); + skip = true; + } + if(!skip && qobject_cast(editor)) + { + connect(editor, SIGNAL(currentIndexChanged (int)), proxy, SLOT(editorDataCommited())); + skip = true; + } + if(!skip && qobject_cast(editor)) + { + connect(editor, SIGNAL(editingFinished()), proxy, SLOT(editorDataCommited())); + skip = true; + } + + connect(proxy, SIGNAL(editorDataCommited(QWidget*, const QModelIndex&, CSMWorld::ColumnBase::Display)), this, SLOT(editorDataCommited(QWidget*, const QModelIndex&, CSMWorld::ColumnBase::Display))); + mProxys.push_back(proxy); //deleted in the destructor + } + return editor; +} + +CSVWorld::DialogueDelegateDispatcher::~DialogueDelegateDispatcher() +{ + for (unsigned i = 0; i < mProxys.size(); ++i) + { + delete mProxys[i]; //unique_ptr could be handy + } +} + +/* +=============================================================EditWidget===================================================== +*/ + +CSVWorld::EditWidget::EditWidget(QWidget *parent, int row, CSMWorld::IdTable* table, QUndoStack& undoStack, bool createAndDelete) : +mDispatcher(this, table, undoStack), +QScrollArea(parent), +mWidgetMapper(NULL), +mMainWidget(NULL), +mUndoStack(undoStack), +mTable(table) +{ + remake (row); + connect(&mDispatcher, SIGNAL(tableMimeDataDropped(QWidget*, const QModelIndex&, const CSMWorld::UniversalId&, const CSMDoc::Document*)), this, SIGNAL(tableMimeDataDropped(QWidget*, const QModelIndex&, const CSMWorld::UniversalId&, const CSMDoc::Document*))); +} + +void CSVWorld::EditWidget::remake(int row) +{ + if (mMainWidget) + { + delete mMainWidget; + } + mMainWidget = new QWidget (this); + + //not sure if widget mapper can handle deleting the widgets that were mapped + if (mWidgetMapper) + { + delete mWidgetMapper; + } + mWidgetMapper = new QDataWidgetMapper (this); + mWidgetMapper->setModel(mTable); + mWidgetMapper->setItemDelegate(&mDispatcher); + + QFrame* line = new QFrame(mMainWidget); + line->setObjectName(QString::fromUtf8("line")); + line->setGeometry(QRect(320, 150, 118, 3)); + line->setFrameShape(QFrame::HLine); + line->setFrameShadow(QFrame::Sunken); + + QVBoxLayout *mainLayout = new QVBoxLayout(mMainWidget); + QGridLayout *unlockedLayout = new QGridLayout(); + QGridLayout *lockedLayout = new QGridLayout(); + mainLayout->addLayout(lockedLayout, 0); + mainLayout->addWidget(line, 1); + mainLayout->addLayout(unlockedLayout, 2); + mainLayout->addStretch(1); + + int unlocked = 0; + int locked = 0; + const int columns = mTable->columnCount(); + for (int i=0; iheaderData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Flags).toInt(); + + if (flags & CSMWorld::ColumnBase::Flag_Dialogue) + { + CSMWorld::ColumnBase::Display display = static_cast + (mTable->headerData (i, Qt::Horizontal, CSMWorld::ColumnBase::Role_Display).toInt()); + + mDispatcher.makeDelegate(display); + QWidget *editor = mDispatcher.makeEditor(display, (mTable->index (row, i))); + + if (editor) + { + mWidgetMapper->addMapping (editor, i); + QLabel* label = new QLabel(mTable->headerData (i, Qt::Horizontal).toString(), mMainWidget); + label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + editor->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); + if (! (mTable->flags (mTable->index (row, i)) & Qt::ItemIsEditable)) + { + lockedLayout->addWidget (label, locked, 0); + lockedLayout->addWidget (editor, locked, 1); + ++locked; + } else + { + unlockedLayout->addWidget (label, unlocked, 0); + unlockedLayout->addWidget (editor, unlocked, 1); + ++unlocked; + } + } + } + } + + mWidgetMapper->setCurrentModelIndex(mTable->index(row, 0)); + + this->setMinimumWidth(325); //TODO find better way to set the width or make it customizable + this->setWidget(mMainWidget); + this->setWidgetResizable(true); +} + +/* +==============================DialogueSubView========================================== +*/ + +CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, + const CreatorFactoryBase& creatorFactory, bool sorting) : + + SubView (id), + mEditWidget(0), + mMainLayout(NULL), + mUndoStack(document.getUndoStack()), + mTable(dynamic_cast(document.getData().getTableModel(id))), + mRow (-1), + mLocked(false), + mDocument(document) + +{ + connect(mTable, SIGNAL(dataChanged (const QModelIndex&, const QModelIndex&)), this, SLOT(dataChanged(const QModelIndex&))); + mRow = mTable->getModelIndex (id.getId(), 0).row(); + QWidget *mainWidget = new QWidget(this); + + QHBoxLayout *buttonsLayout = new QHBoxLayout; + QToolButton* prevButton = new QToolButton(mainWidget); + prevButton->setIcon(QIcon(":/go-previous.png")); + QToolButton* nextButton = new QToolButton(mainWidget); + nextButton->setIcon(QIcon(":/go-next.png")); + buttonsLayout->addWidget(prevButton, 0); + buttonsLayout->addWidget(nextButton, 1); + buttonsLayout->addStretch(2); + + QToolButton* cloneButton = new QToolButton(mainWidget); + cloneButton->setIcon(QIcon(":/edit-clone.png")); + QToolButton* addButton = new QToolButton(mainWidget); + addButton->setIcon(QIcon(":/add.png")); + QToolButton* deleteButton = new QToolButton(mainWidget); + deleteButton->setIcon(QIcon(":/edit-delete.png")); + QToolButton* revertButton = new QToolButton(mainWidget); + revertButton->setIcon(QIcon(":/edit-undo.png")); + + if (mTable->hasPreview()) + { + QToolButton* previewButton = new QToolButton(mainWidget); + previewButton->setIcon(QIcon(":/edit-preview.png")); + buttonsLayout->addWidget(previewButton); + connect(previewButton, SIGNAL(clicked()), this, SLOT(showPreview())); + } + + if (mTable->getViewing()!=CSMWorld::IdTable::Viewing_None) + { + QToolButton* viewButton = new QToolButton(mainWidget); + viewButton->setIcon(QIcon(":/cell.png")); + buttonsLayout->addWidget(viewButton); + connect(viewButton, SIGNAL(clicked()), this, SLOT(viewRecord())); + } + + buttonsLayout->addWidget(cloneButton); + buttonsLayout->addWidget(addButton); + buttonsLayout->addWidget(deleteButton); + buttonsLayout->addWidget(revertButton); + + connect(nextButton, SIGNAL(clicked()), this, SLOT(nextId())); + connect(prevButton, SIGNAL(clicked()), this, SLOT(prevId())); + connect(cloneButton, SIGNAL(clicked()), this, SLOT(cloneRequest())); + connect(revertButton, SIGNAL(clicked()), this, SLOT(revertRecord())); + connect(deleteButton, SIGNAL(clicked()), this, SLOT(deleteRecord())); + + mMainLayout = new QVBoxLayout(mainWidget); + + mEditWidget = new EditWidget(mainWidget, mRow, mTable, mUndoStack, false); + connect(mEditWidget, SIGNAL(tableMimeDataDropped(QWidget*, const QModelIndex&, const CSMWorld::UniversalId&, const CSMDoc::Document*)), + this, SLOT(tableMimeDataDropped(QWidget*, const QModelIndex&, const CSMWorld::UniversalId&, const CSMDoc::Document*))); + + mMainLayout->addWidget(mEditWidget); + mEditWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + + mMainLayout->addWidget (mBottom = + new TableBottomBox (creatorFactory, document.getData(), document.getUndoStack(), id, this)); + + mBottom->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); + connect(mBottom, SIGNAL(requestFocus(const std::string&)), this, SLOT(requestFocus(const std::string&))); + connect(addButton, SIGNAL(clicked()), mBottom, SLOT(createRequest())); + + if(!mBottom->canCreateAndDelete()) + { + cloneButton->setDisabled(true); + addButton->setDisabled(true); + deleteButton->setDisabled(true); + } + + dataChanged(mTable->index(mRow, 0)); + mMainLayout->addLayout(buttonsLayout); + setWidget(mainWidget); +} + +void CSVWorld::DialogueSubView::prevId() +{ + int newRow = mRow - 1; + if (newRow < 0) + { + return; + } + while (newRow >= 0) + { + QModelIndex newIndex(mTable->index(newRow, 0)); + + if (!newIndex.isValid()) + { + return; + } + + CSMWorld::RecordBase::State state = static_cast(mTable->data (mTable->index (newRow, 1)).toInt()); + if (!(state == CSMWorld::RecordBase::State_Deleted || state == CSMWorld::RecordBase::State_Erased)) + { + mEditWidget->remake(newRow); + setUniversalId(CSMWorld::UniversalId (static_cast (mTable->data (mTable->index (newRow, 2)).toInt()), + mTable->data (mTable->index (newRow, 0)).toString().toStdString())); + mRow = newRow; + mEditWidget->setDisabled(mLocked); + return; + } + --newRow; + } +} + +void CSVWorld::DialogueSubView::nextId() +{ + int newRow = mRow + 1; + + if (newRow >= mTable->rowCount()) + { + return; + } + + while (newRow < mTable->rowCount()) + { + QModelIndex newIndex(mTable->index(newRow, 0)); + + if (!newIndex.isValid()) + { + return; + } + + CSMWorld::RecordBase::State state = static_cast(mTable->data (mTable->index (newRow, 1)).toInt()); + if (!(state == CSMWorld::RecordBase::State_Deleted || state == CSMWorld::RecordBase::State_Erased)) + { + mEditWidget->remake(newRow); + setUniversalId(CSMWorld::UniversalId (static_cast (mTable->data (mTable->index (newRow, 2)).toInt()), + mTable->data (mTable->index (newRow, 0)).toString().toStdString())); + mRow = newRow; + mEditWidget->setDisabled(mLocked); + return; + } + ++newRow; + } } void CSVWorld::DialogueSubView::setEditLock (bool locked) { + mLocked = locked; + CSMWorld::RecordBase::State state = static_cast(mTable->data (mTable->index (mRow, 1)).toInt()); + if (state == CSMWorld::RecordBase::State_Deleted || state == CSMWorld::RecordBase::State_Erased) + { + mEditWidget->setDisabled(true); + } else + { + mEditWidget->setDisabled(mLocked); + } +} +void CSVWorld::DialogueSubView::dataChanged(const QModelIndex & index) +{ + if (index.row() == mRow) + { + CSMWorld::RecordBase::State state = static_cast(mTable->data (mTable->index (mRow, 1)).toInt()); + if (state == CSMWorld::RecordBase::State_Deleted || state == CSMWorld::RecordBase::State_Erased) + { + mEditWidget->setDisabled(true); + } else + { + mEditWidget->setDisabled(mLocked); + } + } +} + +void CSVWorld::DialogueSubView::tableMimeDataDropped(QWidget* editor, + const QModelIndex& index, + const CSMWorld::UniversalId& id, + const CSMDoc::Document* document) +{ + if (document == &mDocument) + { + qobject_cast(editor)->setText(id.getId().c_str()); + } +} + +void CSVWorld::DialogueSubView::revertRecord() +{ + int rows = mTable->rowCount(); + if (!mLocked && mTable->columnCount() > 0 && mRow < mTable->rowCount() ) + { + CSMWorld::RecordBase::State state = + static_cast (mTable->data (mTable->index (mRow, 1)).toInt()); + + if (state!=CSMWorld::RecordBase::State_BaseOnly) + { + mUndoStack.push(new CSMWorld::RevertCommand(*mTable, mTable->data(mTable->index (mRow, 0)).toString().toStdString())); + } + if (rows != mTable->rowCount()) + { + if (mTable->rowCount() == 0) + { + mEditWidget->setDisabled(true); //closing the editor is other option + return; + } + if (mRow >= mTable->rowCount()) + { + prevId(); + } else { + dataChanged(mTable->index(mRow, 0)); + } + } + } +} + +void CSVWorld::DialogueSubView::deleteRecord() +{ + int rows = mTable->rowCount(); + + //easier than disabling the button + CSMWorld::RecordBase::State state = static_cast(mTable->data (mTable->index (mRow, 1)).toInt()); + bool deledetedOrErased = (state == CSMWorld::RecordBase::State_Deleted || state == CSMWorld::RecordBase::State_Erased); + + if (!mLocked && + mTable->columnCount() > 0 && + !deledetedOrErased && + mRow < rows && + mBottom->canCreateAndDelete()) + { + mUndoStack.push(new CSMWorld::DeleteCommand(*mTable, mTable->data(mTable->index (mRow, 0)).toString().toStdString())); + if (rows != mTable->rowCount()) + { + if (mTable->rowCount() == 0) + { + mEditWidget->setDisabled(true); //closing the editor is other option + return; + } + if (mRow >= mTable->rowCount()) + { + prevId(); + } else { + dataChanged(mTable->index(mRow, 0)); + } + } + } +} + +void CSVWorld::DialogueSubView::requestFocus (const std::string& id) +{ + mRow = mTable->getModelIndex (id, 0).row(); + mEditWidget->remake(mRow); +} + +void CSVWorld::DialogueSubView::cloneRequest () +{ + mBottom->cloneRequest(mTable->data(mTable->index (mRow, 0)).toString().toStdString(), + static_cast(mTable->data(mTable->index(mRow, 2)).toInt())); +} + +void CSVWorld::DialogueSubView::showPreview () +{ + if (mTable->hasPreview() && mRow < mTable->rowCount()) + { + emit focusId(CSMWorld::UniversalId(CSMWorld::UniversalId::Type_Preview, mTable->data(mTable->index (mRow, 0)).toString().toStdString()), ""); + } +} + +void CSVWorld::DialogueSubView::viewRecord() +{ + if (mRow < mTable->rowCount()) + { + std::pair params = mTable->view (mRow); + + if (params.first.getType()!=CSMWorld::UniversalId::Type_None) + emit focusId (params.first, params.second); + } } \ No newline at end of file diff --git a/apps/opencs/view/world/dialoguesubview.hpp b/apps/opencs/view/world/dialoguesubview.hpp index 64715f5b7..5642f46a0 100644 --- a/apps/opencs/view/world/dialoguesubview.hpp +++ b/apps/opencs/view/world/dialoguesubview.hpp @@ -1,9 +1,25 @@ #ifndef CSV_WORLD_DIALOGUESUBVIEW_H #define CSV_WORLD_DIALOGUESUBVIEW_H +#include +#include + +#include +#include + #include "../doc/subview.hpp" +#include "../../model/world/columnbase.hpp" class QDataWidgetMapper; +class QSize; +class QEvent; +class QLabel; +class QVBoxLayout; + +namespace CSMWorld +{ + class IdTable; +} namespace CSMDoc { @@ -12,15 +28,181 @@ namespace CSMDoc namespace CSVWorld { - class DialogueSubView : public CSVDoc::SubView + class CommandDelegate; + class CreatorFactoryBase; + class TableBottomBox; + + class NotEditableSubDelegate : public QAbstractItemDelegate { + const CSMWorld::IdTable* mTable; + public: + NotEditableSubDelegate(const CSMWorld::IdTable* table, QObject * parent = 0); + + virtual void setEditorData (QLabel* editor, const QModelIndex& index) const; + + virtual void setModelData (QWidget* editor, QAbstractItemModel* model, const QModelIndex& index, CSMWorld::ColumnBase::Display display) const; + + virtual void paint (QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; + ///< does nothing + + virtual QSize sizeHint (const QStyleOptionViewItem& option, const QModelIndex& index) const; + ///< does nothing + + virtual QWidget *createEditor (QWidget *parent, + const QStyleOptionViewItem& option, + const QModelIndex& index, + CSMWorld::ColumnBase::Display display = CSMWorld::ColumnBase::Display_None) const; + }; + + //this can't be nested into the DialogueDelegateDispatcher, because it needs to emit signals + class DialogueDelegateDispatcherProxy : public QObject + { + Q_OBJECT + class refWrapper + { + public: + refWrapper(const QModelIndex& index); + + const QModelIndex& mIndex; + }; + + QWidget* mEditor; + + CSMWorld::ColumnBase::Display mDisplay; + + std::auto_ptr mIndexWrapper; + + public: + DialogueDelegateDispatcherProxy(QWidget* editor, CSMWorld::ColumnBase::Display display); + QWidget* getEditor() const; + + public slots: + void editorDataCommited(); + void setIndex(const QModelIndex& index); + void tableMimeDataDropped(const std::vector& data, const CSMDoc::Document* document); + + signals: + void editorDataCommited(QWidget* editor, const QModelIndex& index, CSMWorld::ColumnBase::Display display); + + void tableMimeDataDropped(QWidget* editor, const QModelIndex& index, + const CSMWorld::UniversalId& id, + const CSMDoc::Document* document); + + }; + + class DialogueDelegateDispatcher : public QAbstractItemDelegate + { + Q_OBJECT + std::map mDelegates; + + QObject* mParent; + + CSMWorld::IdTable* mTable; + + QUndoStack& mUndoStack; + + NotEditableSubDelegate mNotEditableDelegate; + + std::vector mProxys; //once we move to the C++11 we should use unique_ptr + + public: + DialogueDelegateDispatcher(QObject* parent, CSMWorld::IdTable* table, QUndoStack& undoStack); + + ~DialogueDelegateDispatcher(); + + CSVWorld::CommandDelegate* makeDelegate(CSMWorld::ColumnBase::Display display); + + QWidget* makeEditor(CSMWorld::ColumnBase::Display display, const QModelIndex& index); + ///< will return null if delegate is not present, parent of the widget is same as for dispatcher itself + + virtual void setEditorData (QWidget* editor, const QModelIndex& index) const; + + virtual void setModelData (QWidget* editor, QAbstractItemModel* model, const QModelIndex& index, CSMWorld::ColumnBase::Display display) const; + + virtual void paint (QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; + ///< does nothing + + virtual QSize sizeHint (const QStyleOptionViewItem& option, const QModelIndex& index) const; + ///< does nothing + + private slots: + void editorDataCommited(QWidget* editor, const QModelIndex& index, CSMWorld::ColumnBase::Display display); + + signals: + void tableMimeDataDropped(QWidget* editor, const QModelIndex& index, + const CSMWorld::UniversalId& id, + const CSMDoc::Document* document); + + + }; + + class EditWidget : public QScrollArea + { + Q_OBJECT QDataWidgetMapper *mWidgetMapper; + DialogueDelegateDispatcher mDispatcher; + QWidget* mMainWidget; + CSMWorld::IdTable* mTable; + QUndoStack& mUndoStack; public: - DialogueSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, bool createAndDelete); + EditWidget (QWidget *parent, int row, CSMWorld::IdTable* table, QUndoStack& undoStack, bool createAndDelete = false); + + void remake(int row); + + signals: + void tableMimeDataDropped(QWidget* editor, const QModelIndex& index, + const CSMWorld::UniversalId& id, + const CSMDoc::Document* document); + }; + + class DialogueSubView : public CSVDoc::SubView + { + Q_OBJECT + + EditWidget* mEditWidget; + QVBoxLayout* mMainLayout; + CSMWorld::IdTable* mTable; + QUndoStack& mUndoStack; + int mRow; + bool mLocked; + const CSMDoc::Document& mDocument; + TableBottomBox* mBottom; + + public: + + DialogueSubView (const CSMWorld::UniversalId& id, + CSMDoc::Document& document, + const CreatorFactoryBase& creatorFactory, + bool sorting = false); virtual void setEditLock (bool locked); + + private slots: + + void nextId(); + + void prevId(); + + void showPreview(); + + void viewRecord(); + + void revertRecord(); + + void deleteRecord(); + + void cloneRequest(); + + void dataChanged(const QModelIndex & index); + ///\brief we need to care for deleting currently edited record + + void tableMimeDataDropped(QWidget* editor, const QModelIndex& index, + const CSMWorld::UniversalId& id, + const CSMDoc::Document* document); + + void requestFocus (const std::string& id); }; } diff --git a/apps/opencs/view/world/enumdelegate.cpp b/apps/opencs/view/world/enumdelegate.cpp index fc9b7ee3b..377f479bf 100644 --- a/apps/opencs/view/world/enumdelegate.cpp +++ b/apps/opencs/view/world/enumdelegate.cpp @@ -41,10 +41,18 @@ CSVWorld::EnumDelegate::EnumDelegate (const std::vector } -QWidget *CSVWorld::EnumDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem& option, - const QModelIndex& index) const +QWidget *CSVWorld::EnumDelegate::createEditor(QWidget *parent, + const QStyleOptionViewItem& option, + const QModelIndex& index) const { - if (!index.data().isValid()) + return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_None); + //overloading virtual functions is HARD +} + +QWidget *CSVWorld::EnumDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem& option, + const QModelIndex& index, CSMWorld::ColumnBase::Display display) const +{ + if (!index.data(Qt::EditRole).isValid() && !index.data(Qt::DisplayRole).isValid()) return 0; QComboBox *comboBox = new QComboBox (parent); @@ -56,11 +64,22 @@ QWidget *CSVWorld::EnumDelegate::createEditor(QWidget *parent, const QStyleOptio return comboBox; } -void CSVWorld::EnumDelegate::setEditorData (QWidget *editor, const QModelIndex& index) const +void CSVWorld::EnumDelegate::setEditorData (QWidget *editor, const QModelIndex& index, bool tryDisplay) const { if (QComboBox *comboBox = dynamic_cast (editor)) { - int value = index.data (Qt::EditRole).toInt(); + QVariant data = index.data (Qt::EditRole); + + if (tryDisplay && !data.isValid()) + { + data = index.data (Qt::DisplayRole); + if (!data.isValid()) + { + return; + } + } + + int value = data.toInt(); std::size_t size = mValues.size(); diff --git a/apps/opencs/view/world/enumdelegate.hpp b/apps/opencs/view/world/enumdelegate.hpp index 606f9278a..cd749a451 100644 --- a/apps/opencs/view/world/enumdelegate.hpp +++ b/apps/opencs/view/world/enumdelegate.hpp @@ -4,6 +4,7 @@ #include #include +#include #include @@ -31,10 +32,16 @@ namespace CSVWorld EnumDelegate (const std::vector >& values, QUndoStack& undoStack, QObject *parent); - virtual QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem& option, - const QModelIndex& index) const; + virtual QWidget *createEditor(QWidget *parent, + const QStyleOptionViewItem& option, + const QModelIndex& index) const; - virtual void setEditorData (QWidget *editor, const QModelIndex& index) const; + virtual QWidget *createEditor(QWidget *parent, + const QStyleOptionViewItem& option, + const QModelIndex& index, + CSMWorld::ColumnBase::Display display = CSMWorld::ColumnBase::Display_None) const; + + virtual void setEditorData (QWidget *editor, const QModelIndex& index, bool tryDisplay = false) const; virtual void paint (QPainter *painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; diff --git a/apps/opencs/view/world/subviews.cpp b/apps/opencs/view/world/subviews.cpp index 33a91330d..75f391699 100644 --- a/apps/opencs/view/world/subviews.cpp +++ b/apps/opencs/view/world/subviews.cpp @@ -80,5 +80,61 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) manager.add (CSMWorld::UniversalId::Type_Scene, new CSVDoc::SubViewFactory); + //edit subviews + manager.add (CSMWorld::UniversalId::Type_Region, + new CSVDoc::SubViewFactoryWithCreator > (false)); + + manager.add (CSMWorld::UniversalId::Type_Spell, + new CSVDoc::SubViewFactoryWithCreator > (false)); + + manager.add (CSMWorld::UniversalId::Type_Referenceable, + new CSVDoc::SubViewFactoryWithCreator > (false)); + + manager.add (CSMWorld::UniversalId::Type_Birthsign, + new CSVDoc::SubViewFactoryWithCreator > (false)); + + manager.add (CSMWorld::UniversalId::Type_Global, + new CSVDoc::SubViewFactoryWithCreator > (false)); + + manager.add (CSMWorld::UniversalId::Type_Gmst, + new CSVDoc::SubViewFactoryWithCreator > (false)); + + manager.add (CSMWorld::UniversalId::Type_Race, + new CSVDoc::SubViewFactoryWithCreator > (false)); + + manager.add (CSMWorld::UniversalId::Type_Class, + new CSVDoc::SubViewFactoryWithCreator > (false)); + + manager.add (CSMWorld::UniversalId::Type_Reference, + new CSVDoc::SubViewFactoryWithCreator > (false)); + + manager.add (CSMWorld::UniversalId::Type_Cell, + new CSVDoc::SubViewFactoryWithCreator > (false)); + + manager.add (CSMWorld::UniversalId::Type_Filter, + new CSVDoc::SubViewFactoryWithCreator > (false)); + + manager.add (CSMWorld::UniversalId::Type_Sound, + new CSVDoc::SubViewFactoryWithCreator > (false)); + + manager.add (CSMWorld::UniversalId::Type_Faction, + new CSVDoc::SubViewFactoryWithCreator > (false)); + + manager.add (CSMWorld::UniversalId::Type_Skill, + new CSVDoc::SubViewFactoryWithCreator > (false)); + + manager.add (CSMWorld::UniversalId::Type_JournalInfo, + new CSVDoc::SubViewFactoryWithCreator > (false)); + + manager.add (CSMWorld::UniversalId::Type_TopicInfo, + new CSVDoc::SubViewFactoryWithCreator >(false)); + + manager.add (CSMWorld::UniversalId::Type_Topic, + new CSVDoc::SubViewFactoryWithCreator (false)); + + manager.add (CSMWorld::UniversalId::Type_Journal, + new CSVDoc::SubViewFactoryWithCreator (false)); + + //preview manager.add (CSMWorld::UniversalId::Type_Preview, new CSVDoc::SubViewFactory); } \ No newline at end of file diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 29bca2f6b..cd9138c35 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -442,6 +442,7 @@ void CSVWorld::Table::previewRecord() emit editRequest (CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Preview, id) , ""); } } + void CSVWorld::Table::updateEditorSetting (const QString &settingName, const QString &settingValue) { int columns = mModel->columnCount(); @@ -533,27 +534,14 @@ void CSVWorld::Table::mouseMoveEvent (QMouseEvent* event) drag->setMimeData (mime); drag->setPixmap (QString::fromStdString (mime->getIcon())); - - Qt::DropActions action = Qt::IgnoreAction; - switch (QApplication::keyboardModifiers()) - { - case Qt::ControlModifier: - action = Qt::CopyAction; - break; - - case Qt::ShiftModifier: - action = Qt::MoveAction; - break; - } - - drag->exec(action); + drag->exec(Qt::CopyAction); } } void CSVWorld::Table::dragEnterEvent(QDragEnterEvent *event) { - event->acceptProposedAction(); + event->acceptProposedAction(); } void CSVWorld::Table::dropEvent(QDropEvent *event) @@ -585,7 +573,7 @@ void CSVWorld::Table::dropEvent(QDropEvent *event) void CSVWorld::Table::dragMoveEvent(QDragMoveEvent *event) { - event->accept(); + event->accept(); } std::vector CSVWorld::Table::getColumnsWithDisplay(CSMWorld::ColumnBase::Display display) const diff --git a/apps/opencs/view/world/util.cpp b/apps/opencs/view/world/util.cpp index 97af3b99c..227c5c9c5 100644 --- a/apps/opencs/view/world/util.cpp +++ b/apps/opencs/view/world/util.cpp @@ -4,8 +4,18 @@ #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "../../model/world/commands.hpp" +#include "../../model/world/tablemimedata.hpp" CSVWorld::NastyTableModelHack::NastyTableModelHack (QAbstractItemModel& model) : mModel (model) @@ -117,10 +127,56 @@ void CSVWorld::CommandDelegate::setModelData (QWidget *editor, QAbstractItemMode } QWidget *CSVWorld::CommandDelegate::createEditor (QWidget *parent, const QStyleOptionViewItem& option, - const QModelIndex& index) const + const QModelIndex& index, CSMWorld::ColumnBase::Display display) const { - if (!index.data().isValid()) - return 0; + QVariant variant = index.data(); + if (!variant.isValid()) + { + variant = index.data(Qt::DisplayRole); + if (!variant.isValid()) + { + return 0; + } + } + + if (display != CSMWorld::ColumnBase::Display_None) + { + if (variant.type() == QVariant::Color) + { + return new QLineEdit(parent); + } + if (display == CSMWorld::ColumnBase::Display_Integer) + { + return new QSpinBox(parent); + } + if (display == CSMWorld::ColumnBase::Display_Var) + { + return new QLineEdit(parent); + } + if (display == CSMWorld::ColumnBase::Display_Float) + { + return new QDoubleSpinBox(parent); + } + if (display == CSMWorld::ColumnBase::Display_LongString) + { + return new QTextEdit(parent); + } + if (display == CSMWorld::ColumnBase::Display_String || + display == CSMWorld::ColumnBase::Display_Skill || + display == CSMWorld::ColumnBase::Display_Script || + display == CSMWorld::ColumnBase::Display_Race || + display == CSMWorld::ColumnBase::Display_Class || + display == CSMWorld::ColumnBase::Display_Faction || + display == CSMWorld::ColumnBase::Display_Miscellaneous || + display == CSMWorld::ColumnBase::Display_Sound) + { + return new DropLineEdit(parent); + } + if (display == CSMWorld::ColumnBase::Display_Boolean) + { + return new QCheckBox(parent); + } + } return QStyledItemDelegate::createEditor (parent, option, index); } @@ -140,4 +196,67 @@ bool CSVWorld::CommandDelegate::updateEditorSetting (const QString &settingName, const QString &settingValue) { return false; +} + +void CSVWorld::CommandDelegate::setEditorData (QWidget *editor, const QModelIndex& index, bool tryDisplay) const +{ + QVariant v = index.data(Qt::EditRole); + if (tryDisplay) + { + if (!v.isValid()) + { + v = index.data(Qt::DisplayRole); + if (!v.isValid()) + { + return; + } + } + QPlainTextEdit* plainTextEdit = qobject_cast(editor); + if(plainTextEdit) //for some reason it is easier to brake the loop here + { + if(plainTextEdit->toPlainText() == v.toString()) + { + return; + } + } + } + + QByteArray n = editor->metaObject()->userProperty().name(); + + if (n == "dateTime") { + if (editor->inherits("QTimeEdit")) + n = "time"; + else if (editor->inherits("QDateEdit")) + n = "date"; + } + + if (!n.isEmpty()) { + if (!v.isValid()) + v = QVariant(editor->property(n).userType(), (const void *)0); + editor->setProperty(n, v); + } + +} + +CSVWorld::DropLineEdit::DropLineEdit(QWidget* parent) : +QLineEdit(parent) +{ + setAcceptDrops(true); +} + +void CSVWorld::DropLineEdit::dragEnterEvent(QDragEnterEvent *event) +{ + event->acceptProposedAction(); +} + +void CSVWorld::DropLineEdit::dragMoveEvent(QDragMoveEvent *event) +{ + event->accept(); +} + +void CSVWorld::DropLineEdit::dropEvent(QDropEvent *event) +{ + const CSMWorld::TableMimeData* data(dynamic_cast(event->mimeData())); + emit tableMimeDataDropped(data->getData(), data->getDocumentPtr()); + //WIP } \ No newline at end of file diff --git a/apps/opencs/view/world/util.hpp b/apps/opencs/view/world/util.hpp index 87f118cd7..7664f3eae 100644 --- a/apps/opencs/view/world/util.hpp +++ b/apps/opencs/view/world/util.hpp @@ -5,11 +5,19 @@ #include #include +#include #include "../../model/world/columnbase.hpp" +#include "../../model/doc/document.hpp" class QUndoStack; +namespace CSMWorld +{ + class TableMimeData; + class UniversalId; +} + namespace CSVWorld { ///< \brief Getting the data out of an editor widget @@ -79,6 +87,24 @@ namespace CSVWorld }; + class DropLineEdit : public QLineEdit + { + Q_OBJECT + + public: + DropLineEdit(QWidget *parent); + + private: + void dragEnterEvent(QDragEnterEvent *event); + + void dragMoveEvent(QDragMoveEvent *event); + + void dropEvent(QDropEvent *event); + + signals: + void tableMimeDataDropped(const std::vector& data, const CSMDoc::Document* document); + }; + ///< \brief Use commands instead of manipulating the model directly class CommandDelegate : public QStyledItemDelegate { @@ -101,8 +127,10 @@ namespace CSVWorld virtual void setModelData (QWidget *editor, QAbstractItemModel *model, const QModelIndex& index) const; - virtual QWidget *createEditor (QWidget *parent, const QStyleOptionViewItem& option, - const QModelIndex& index) const; + virtual QWidget *createEditor (QWidget *parent, + const QStyleOptionViewItem& option, + const QModelIndex& index, + CSMWorld::ColumnBase::Display display = CSMWorld::ColumnBase::Display_None) const; void setEditLock (bool locked); @@ -111,6 +139,9 @@ namespace CSVWorld virtual bool updateEditorSetting (const QString &settingName, const QString &settingValue); ///< \return Does column require update? + virtual void setEditorData (QWidget *editor, const QModelIndex& index, bool tryDisplay = false) const; + + private slots: virtual void slotUpdateEditorSetting (const QString &settingName, const QString &settingValue) {} diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index eb502de38..20011b0d9 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -15,7 +15,7 @@ add_openmw_dir (mwrender renderingmanager debugging sky camera animation npcanimation creatureanimation activatoranimation actors objects renderinginterface localmap occlusionquery water shadows characterpreview globalmap videoplayer ripplesimulation refraction - terrainstorage renderconst effectmanager + terrainstorage renderconst effectmanager weaponanimation ) add_openmw_dir (mwinput @@ -44,7 +44,7 @@ add_openmw_dir (mwscript locals scriptmanagerimp compilercontext interpretercontext cellextensions miscextensions guiextensions soundextensions skyextensions statsextensions containerextensions aiextensions controlextensions extensions globalscripts ref dialogueextensions - animationextensions transformationextensions consoleextensions userextensions locals + animationextensions transformationextensions consoleextensions userextensions ) add_openmw_dir (mwsound diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 37821b990..47e0d016f 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -359,7 +359,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) // Create the world mEnvironment.setWorld( new MWWorld::World (*mOgre, mFileCollections, mContentFiles, mResDir, mCfgMgr.getCachePath(), mEncoder, mFallbackMap, - mActivationDistanceOverride)); + mActivationDistanceOverride, mCellName)); MWBase::Environment::get().getWorld()->setupPlayer(); input->setPlayer(&mEnvironment.getWorld()->getPlayer()); @@ -395,31 +395,6 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) mechanics->buildPlayer(); window->updatePlayer(); - // load cell - ESM::Position pos; - MWBase::World *world = MWBase::Environment::get().getWorld(); - - if (!mCellName.empty()) - { - if (world->findExteriorPosition(mCellName, pos)) { - world->changeToExteriorCell (pos); - } - else { - world->findInteriorPosition(mCellName, pos); - world->changeToInteriorCell (mCellName, pos); - } - } - else - { - pos.pos[0] = pos.pos[1] = pos.pos[2] = 0; - pos.rot[0] = pos.rot[1] = pos.rot[2] = 0; - world->changeToExteriorCell (pos); - } - - Ogre::FrameEvent event; - event.timeSinceLastEvent = 0; - event.timeSinceLastFrame = 0; - frameRenderingQueued(event); mOgre->getRoot()->addFrameListener (this); // scripts @@ -457,15 +432,15 @@ void OMW::Engine::go() // Play some good 'ol tunes MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore")); - if (!mStartupScript.empty()) - MWBase::Environment::get().getWindowManager()->executeInConsole (mStartupScript); - // start in main menu if (!mSkipMenu) MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); else MWBase::Environment::get().getStateManager()->newGame (true); + if (!mStartupScript.empty()) + MWBase::Environment::get().getWindowManager()->executeInConsole (mStartupScript); + // Start the main rendering loop while (!mEnvironment.get().getStateManager()->hasQuitRequest()) Ogre::Root::getSingleton().renderOneFrame(); diff --git a/apps/openmw/mwbase/inputmanager.hpp b/apps/openmw/mwbase/inputmanager.hpp index 8293cbfa7..42922a5b3 100644 --- a/apps/openmw/mwbase/inputmanager.hpp +++ b/apps/openmw/mwbase/inputmanager.hpp @@ -20,6 +20,9 @@ namespace MWBase InputManager() {} + /// Clear all savegame-specific data + virtual void clear() = 0; + virtual ~InputManager() {} virtual void update(float dt, bool loading) = 0; diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 5c2912a8f..bb6f5741d 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -101,7 +101,8 @@ namespace MWBase virtual ~World() {} - virtual void startNewGame() = 0; + virtual void startNewGame (bool bypass) = 0; + ///< \param bypass Bypass regular game start. virtual void clear() = 0; diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 715784b7c..604b51990 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -28,16 +28,16 @@ namespace { - struct CustomData : public MWWorld::CustomData + struct ContainerCustomData : public MWWorld::CustomData { MWWorld::ContainerStore mContainerStore; virtual MWWorld::CustomData *clone() const; }; - MWWorld::CustomData *CustomData::clone() const + MWWorld::CustomData *ContainerCustomData::clone() const { - return new CustomData (*this); + return new ContainerCustomData (*this); } } @@ -47,7 +47,7 @@ namespace MWClass { if (!ptr.getRefData().getCustomData()) { - std::auto_ptr data (new CustomData); + std::auto_ptr data (new ContainerCustomData); MWWorld::LiveCellRef *ref = ptr.get(); @@ -174,7 +174,7 @@ namespace MWClass { ensureCustomData (ptr); - return dynamic_cast (*ptr.getRefData().getCustomData()).mContainerStore; + return dynamic_cast (*ptr.getRefData().getCustomData()).mContainerStore; } std::string Container::getScript (const MWWorld::Ptr& ptr) const @@ -267,7 +267,7 @@ namespace MWClass ensureCustomData (ptr); - dynamic_cast (*ptr.getRefData().getCustomData()).mContainerStore. + dynamic_cast (*ptr.getRefData().getCustomData()).mContainerStore. readState (state2.mInventory); } @@ -278,7 +278,7 @@ namespace MWClass ensureCustomData (ptr); - dynamic_cast (*ptr.getRefData().getCustomData()).mContainerStore. + dynamic_cast (*ptr.getRefData().getCustomData()).mContainerStore. writeState (state2.mInventory); } } diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 33c4390a0..0f7ffdc48 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -37,7 +37,7 @@ namespace { - struct CustomData : public MWWorld::CustomData + struct CreatureCustomData : public MWWorld::CustomData { MWMechanics::CreatureStats mCreatureStats; MWWorld::ContainerStore* mContainerStore; // may be InventoryStore for some creatures @@ -45,13 +45,13 @@ namespace virtual MWWorld::CustomData *clone() const; - CustomData() : mContainerStore(0) {} - virtual ~CustomData() { delete mContainerStore; } + CreatureCustomData() : mContainerStore(0) {} + virtual ~CreatureCustomData() { delete mContainerStore; } }; - MWWorld::CustomData *CustomData::clone() const + MWWorld::CustomData *CreatureCustomData::clone() const { - CustomData* cloned = new CustomData (*this); + CreatureCustomData* cloned = new CreatureCustomData (*this); cloned->mContainerStore = mContainerStore->clone(); return cloned; } @@ -63,7 +63,7 @@ namespace MWClass { if (!ptr.getRefData().getCustomData()) { - std::auto_ptr data (new CustomData); + std::auto_ptr data (new CreatureCustomData); static bool inited = false; if(!inited) @@ -192,7 +192,7 @@ namespace MWClass { ensureCustomData (ptr); - return dynamic_cast (*ptr.getRefData().getCustomData()).mCreatureStats; + return dynamic_cast (*ptr.getRefData().getCustomData()).mCreatureStats; } @@ -456,7 +456,7 @@ namespace MWClass { ensureCustomData (ptr); - return *dynamic_cast (*ptr.getRefData().getCustomData()).mContainerStore; + return *dynamic_cast (*ptr.getRefData().getCustomData()).mContainerStore; } MWWorld::InventoryStore& Creature::getInventoryStore(const MWWorld::Ptr &ptr) const @@ -525,7 +525,7 @@ namespace MWClass float moveSpeed; if(normalizedEncumbrance >= 1.0f) moveSpeed = 0.0f; - else if(isFlying(ptr) || (mageffects.get(ESM::MagicEffect::Levitate).mMagnitude > 0 && + else if(canFly(ptr) || (mageffects.get(ESM::MagicEffect::Levitate).mMagnitude > 0 && world->isLevitationEnabled())) { float flySpeed = 0.01f*(stats.getAttribute(ESM::Attribute::Speed).getModified() + @@ -559,7 +559,7 @@ namespace MWClass { ensureCustomData (ptr); - return dynamic_cast (*ptr.getRefData().getCustomData()).mMovement; + return dynamic_cast (*ptr.getRefData().getCustomData()).mMovement; } Ogre::Vector3 Creature::getMovementVector (const MWWorld::Ptr& ptr) const @@ -678,7 +678,15 @@ namespace MWClass return MWWorld::Ptr(&cell.get().insert(*ref), &cell); } - bool Creature::isFlying(const MWWorld::Ptr &ptr) const + bool Creature::isBipedal(const MWWorld::Ptr &ptr) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + + return ref->mBase->mFlags & ESM::Creature::Bipedal; + } + + bool Creature::canFly(const MWWorld::Ptr &ptr) const { MWWorld::LiveCellRef *ref = ptr.get(); @@ -686,6 +694,22 @@ namespace MWClass return ref->mBase->mFlags & ESM::Creature::Flies; } + bool Creature::canSwim(const MWWorld::Ptr &ptr) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + + return ref->mBase->mFlags & ESM::Creature::Swims; + } + + bool Creature::canWalk(const MWWorld::Ptr &ptr) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + + return ref->mBase->mFlags & ESM::Creature::Walks; + } + int Creature::getSndGenTypeFromName(const MWWorld::Ptr &ptr, const std::string &name) { if(name == "left") @@ -762,7 +786,7 @@ namespace MWClass ensureCustomData (ptr); - CustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); + CreatureCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); customData.mContainerStore->readState (state2.mInventory); customData.mCreatureStats.readState (state2.mCreatureStats); @@ -776,7 +800,7 @@ namespace MWClass ensureCustomData (ptr); - CustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); + CreatureCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); customData.mContainerStore->writeState (state2.mInventory); customData.mCreatureStats.writeState (state2.mCreatureStats); diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index 6df6db297..c1bcb8739 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -124,7 +124,10 @@ namespace MWClass return true; } - virtual bool isFlying (const MWWorld::Ptr &ptr) const; + virtual bool isBipedal (const MWWorld::Ptr &ptr) const; + virtual bool canFly (const MWWorld::Ptr &ptr) const; + virtual bool canSwim (const MWWorld::Ptr &ptr) const; + virtual bool canWalk (const MWWorld::Ptr &ptr) const; virtual int getSkill(const MWWorld::Ptr &ptr, int skill) const; diff --git a/apps/openmw/mwclass/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp index caef521af..732038b2f 100644 --- a/apps/openmw/mwclass/creaturelevlist.cpp +++ b/apps/openmw/mwclass/creaturelevlist.cpp @@ -9,15 +9,15 @@ namespace { - struct CustomData : public MWWorld::CustomData + struct CreatureLevListCustomData : public MWWorld::CustomData { // TODO: save the creature we spawned here virtual MWWorld::CustomData *clone() const; }; - MWWorld::CustomData *CustomData::clone() const + MWWorld::CustomData *CreatureLevListCustomData::clone() const { - return new CustomData (*this); + return new CreatureLevListCustomData (*this); } } @@ -44,7 +44,7 @@ namespace MWClass { if (!ptr.getRefData().getCustomData()) { - std::auto_ptr data (new CustomData); + std::auto_ptr data (new CreatureLevListCustomData); MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index 72de620e4..bd25b66b2 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -26,12 +26,12 @@ namespace { - struct CustomData : public MWWorld::CustomData + struct LightCustomData : public MWWorld::CustomData { float mTime; ///< Time remaining - CustomData(MWWorld::Ptr ptr) + LightCustomData(MWWorld::Ptr ptr) { MWWorld::LiveCellRef *ref = ptr.get(); mTime = ref->mBase->mData.mTime; @@ -40,7 +40,7 @@ namespace virtual MWWorld::CustomData *clone() const { - return new CustomData (*this); + return new LightCustomData (*this); } }; } @@ -210,7 +210,7 @@ namespace MWClass { ensureCustomData(ptr); - float &timeRemaining = dynamic_cast (*ptr.getRefData().getCustomData()).mTime; + float &timeRemaining = dynamic_cast (*ptr.getRefData().getCustomData()).mTime; timeRemaining = duration; } @@ -218,7 +218,7 @@ namespace MWClass { ensureCustomData(ptr); - return dynamic_cast (*ptr.getRefData().getCustomData()).mTime; + return dynamic_cast (*ptr.getRefData().getCustomData()).mTime; } MWWorld::Ptr @@ -233,7 +233,7 @@ namespace MWClass void Light::ensureCustomData (const MWWorld::Ptr& ptr) const { if (!ptr.getRefData().getCustomData()) - ptr.getRefData().setCustomData(new CustomData(ptr)); + ptr.getRefData().setCustomData(new LightCustomData(ptr)); } bool Light::canSell (const MWWorld::Ptr& item, int npcServices) const @@ -278,7 +278,7 @@ namespace MWClass ensureCustomData (ptr); - dynamic_cast (*ptr.getRefData().getCustomData()).mTime = state2.mTime; + dynamic_cast (*ptr.getRefData().getCustomData()).mTime = state2.mTime; } void Light::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) @@ -288,6 +288,6 @@ namespace MWClass ensureCustomData (ptr); - state2.mTime = dynamic_cast (*ptr.getRefData().getCustomData()).mTime; + state2.mTime = dynamic_cast (*ptr.getRefData().getCustomData()).mTime; } } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 50e002113..3fbd0d5b2 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -39,7 +39,7 @@ namespace { - struct CustomData : public MWWorld::CustomData + struct NpcCustomData : public MWWorld::CustomData { MWMechanics::NpcStats mNpcStats; MWMechanics::Movement mMovement; @@ -48,9 +48,9 @@ namespace virtual MWWorld::CustomData *clone() const; }; - MWWorld::CustomData *CustomData::clone() const + MWWorld::CustomData *NpcCustomData::clone() const { - return new CustomData (*this); + return new NpcCustomData (*this); } void autoCalculateAttributes (const ESM::NPC* npc, MWMechanics::CreatureStats& creatureStats) @@ -262,7 +262,7 @@ namespace MWClass } if (!ptr.getRefData().getCustomData()) { - std::auto_ptr data(new CustomData); + std::auto_ptr data(new NpcCustomData); MWWorld::LiveCellRef *ref = ptr.get(); @@ -436,14 +436,14 @@ namespace MWClass { ensureCustomData (ptr); - return dynamic_cast (*ptr.getRefData().getCustomData()).mNpcStats; + return dynamic_cast (*ptr.getRefData().getCustomData()).mNpcStats; } MWMechanics::NpcStats& Npc::getNpcStats (const MWWorld::Ptr& ptr) const { ensureCustomData (ptr); - return dynamic_cast (*ptr.getRefData().getCustomData()).mNpcStats; + return dynamic_cast (*ptr.getRefData().getCustomData()).mNpcStats; } @@ -673,12 +673,7 @@ namespace MWClass else getCreatureStats(ptr).setHitRecovery(true); // Is this supposed to always occur? - if(object.isEmpty()) - { - if(ishealth) - damage /= std::min(1.0f + getArmorRating(ptr)/std::max(1.0f, damage), 4.0f); - } - else if(ishealth) + if(ishealth) { // Hit percentages: // cuirass = 30% @@ -824,7 +819,7 @@ namespace MWClass { ensureCustomData (ptr); - return dynamic_cast (*ptr.getRefData().getCustomData()).mInventoryStore; + return dynamic_cast (*ptr.getRefData().getCustomData()).mInventoryStore; } MWWorld::InventoryStore& Npc::getInventoryStore (const MWWorld::Ptr& ptr) @@ -832,7 +827,7 @@ namespace MWClass { ensureCustomData (ptr); - return dynamic_cast (*ptr.getRefData().getCustomData()).mInventoryStore; + return dynamic_cast (*ptr.getRefData().getCustomData()).mInventoryStore; } std::string Npc::getScript (const MWWorld::Ptr& ptr) const @@ -846,7 +841,7 @@ namespace MWClass float Npc::getSpeed(const MWWorld::Ptr& ptr) const { const MWBase::World *world = MWBase::Environment::get().getWorld(); - const CustomData *npcdata = static_cast(ptr.getRefData().getCustomData()); + const NpcCustomData *npcdata = static_cast(ptr.getRefData().getCustomData()); const MWMechanics::MagicEffects &mageffects = npcdata->mNpcStats.getMagicEffects(); const float normalizedEncumbrance = Npc::getEncumbrance(ptr) / Npc::getCapacity(ptr); @@ -901,7 +896,7 @@ namespace MWClass float Npc::getJump(const MWWorld::Ptr &ptr) const { - const CustomData *npcdata = static_cast(ptr.getRefData().getCustomData()); + const NpcCustomData *npcdata = static_cast(ptr.getRefData().getCustomData()); const MWMechanics::MagicEffects &mageffects = npcdata->mNpcStats.getMagicEffects(); const float encumbranceTerm = fJumpEncumbranceBase->getFloat() + fJumpEncumbranceMultiplier->getFloat() * @@ -940,7 +935,7 @@ namespace MWClass if (fallHeight >= fallDistanceMin) { const float acrobaticsSkill = MWWorld::Class::get(ptr).getNpcStats (ptr).getSkill(ESM::Skill::Acrobatics).getModified(); - const CustomData *npcdata = static_cast(ptr.getRefData().getCustomData()); + const NpcCustomData *npcdata = static_cast(ptr.getRefData().getCustomData()); const float jumpSpellBonus = npcdata->mNpcStats.getMagicEffects().get(ESM::MagicEffect::Jump).mMagnitude; const float fallAcroBase = gmst.find("fFallAcroBase")->getFloat(); const float fallAcroMult = gmst.find("fFallAcroMult")->getFloat(); @@ -965,7 +960,7 @@ namespace MWClass { ensureCustomData (ptr); - return dynamic_cast (*ptr.getRefData().getCustomData()).mMovement; + return dynamic_cast (*ptr.getRefData().getCustomData()).mMovement; } Ogre::Vector3 Npc::getMovementVector (const MWWorld::Ptr& ptr) const @@ -1271,7 +1266,7 @@ namespace MWClass ensureCustomData (ptr); - CustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); + NpcCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); customData.mInventoryStore.readState (state2.mInventory); customData.mNpcStats.readState (state2.mNpcStats); @@ -1285,7 +1280,7 @@ namespace MWClass ensureCustomData (ptr); - CustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); + NpcCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); customData.mInventoryStore.writeState (state2.mInventory); customData.mNpcStats.writeState (state2.mNpcStats); diff --git a/apps/openmw/mwgui/alchemywindow.cpp b/apps/openmw/mwgui/alchemywindow.cpp index ddbd3f120..a6880ffcb 100644 --- a/apps/openmw/mwgui/alchemywindow.cpp +++ b/apps/openmw/mwgui/alchemywindow.cpp @@ -26,16 +26,6 @@ namespace return path; } - std::string getCountString(const int count) - { - if (count == 1) - return ""; - if (count > 9999) - return boost::lexical_cast(int(count/1000.f)) + "k"; - else - return boost::lexical_cast(count); - } - } namespace MWGui @@ -226,7 +216,7 @@ namespace MWGui text->setNeedMouseFocus(false); text->setTextShadow(true); text->setTextShadowColour(MyGUI::Colour(0,0,0)); - text->setCaption(getCountString(ingredient->getUserData()->getRefData().getCount())); + text->setCaption(ItemView::getCountString(ingredient->getUserData()->getRefData().getCount())); } mItemView->update(); diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index 7e7ad5ec2..34ac8d9f4 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -23,19 +23,6 @@ #include "sortfilteritemmodel.hpp" #include "pickpocketitemmodel.hpp" -namespace -{ - std::string getCountString(const int count) - { - if (count == 1) - return ""; - if (count > 9999) - return boost::lexical_cast(int(count/1000.f)) + "k"; - else - return boost::lexical_cast(count); - } -} - namespace MWGui { @@ -79,7 +66,7 @@ namespace MWGui text->setNeedMouseFocus(false); text->setTextShadow(true); text->setTextShadowColour(MyGUI::Colour(0,0,0)); - text->setCaption(getCountString(count)); + text->setCaption(ItemView::getCountString(count)); sourceView->update(); diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index 481c98314..6b913f24a 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -23,18 +23,7 @@ #include "travelwindow.hpp" #include "bookpage.hpp" - -namespace -{ - MWGui::BookTypesetter::Utf8Span to_utf8_span (char const * text) - { - typedef MWGui::BookTypesetter::Utf8Point point; - - point begin = reinterpret_cast (text); - - return MWGui::BookTypesetter::Utf8Span (begin, begin + strlen (text)); - } -} +#include "journalbooks.hpp" // to_utf8_span namespace MWGui { diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index a6ad43ce5..06a228a1f 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -604,15 +604,22 @@ namespace MWGui mEffectBox->setPosition((viewSize.width - mEffectBoxBaseRight) - mEffectBox->getWidth() + effectsDx, mEffectBox->getTop()); } + void HUD::updateEnemyHealthBar() + { + MWMechanics::CreatureStats& stats = MWWorld::Class::get(mEnemy).getCreatureStats(mEnemy); + mEnemyHealth->setProgressRange(100); + // Health is usually cast to int before displaying. Actors die whenever they are < 1 health. + // Therefore any value < 1 should show as an empty health bar. We do the same in statswindow :) + mEnemyHealth->setProgressPosition(int(stats.getHealth().getCurrent()) / stats.getHealth().getModified() * 100); + } + void HUD::update() { mSpellIcons->updateWidgets(mEffectBox, true); if (!mEnemy.isEmpty() && mEnemyHealth->getVisible()) { - MWMechanics::CreatureStats& stats = MWWorld::Class::get(mEnemy).getCreatureStats(mEnemy); - mEnemyHealth->setProgressRange(100); - mEnemyHealth->setProgressPosition(stats.getHealth().getCurrent() / stats.getHealth().getModified() * 100); + updateEnemyHealthBar(); } if (mIsDrowning) @@ -629,9 +636,7 @@ namespace MWGui if (!mEnemyHealth->getVisible()) mWeaponSpellBox->setPosition(mWeaponSpellBox->getPosition() - MyGUI::IntPoint(0,20)); mEnemyHealth->setVisible(true); - MWMechanics::CreatureStats& stats = MWWorld::Class::get(mEnemy).getCreatureStats(mEnemy); - mEnemyHealth->setProgressRange(100); - mEnemyHealth->setProgressPosition(stats.getHealth().getCurrent() / stats.getHealth().getModified() * 100); + updateEnemyHealthBar(); } } diff --git a/apps/openmw/mwgui/hud.hpp b/apps/openmw/mwgui/hud.hpp index 04206fbc8..1645d8db0 100644 --- a/apps/openmw/mwgui/hud.hpp +++ b/apps/openmw/mwgui/hud.hpp @@ -1,3 +1,6 @@ +#ifndef OPENMW_GAME_MWGUI_HUD_H +#define OPENMW_GAME_MWGUI_HUD_H + #include "mapwindow.hpp" #include "../mwmechanics/stat.hpp" @@ -112,6 +115,10 @@ namespace MWGui void onMagicClicked(MyGUI::Widget* _sender); void onMapClicked(MyGUI::Widget* _sender); + void updateEnemyHealthBar(); + void updatePositions(); }; } + +#endif diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 2ea09db61..e9efe75e7 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -162,6 +162,7 @@ namespace MWGui } const ItemStack& item = mTradeModel->getItem(index); + std::string sound = MWWorld::Class::get(item.mBase).getDownSoundId(item.mBase); MWWorld::Ptr object = item.mBase; int count = item.mCount; @@ -170,6 +171,7 @@ namespace MWGui if (item.mBase.getCellRef().mRefID.size() > 6 && item.mBase.getCellRef().mRefID.substr(0,6) == "bound_") { + MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); MWBase::Environment::get().getWindowManager()->messageBox("#{sBarterDialog12}"); return; } @@ -213,6 +215,7 @@ namespace MWGui int services = MWBase::Environment::get().getWindowManager()->getTradeWindow()->getMerchantServices(); if (!MWWorld::Class::get(object).canSell(object, services)) { + MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); MWBase::Environment::get().getWindowManager()-> messageBox("#{sBarterDialog4}"); return; @@ -514,6 +517,9 @@ namespace MWGui void InventoryWindow::pickUpObject (MWWorld::Ptr object) { + // If the inventory is not yet enabled, don't pick anything up + if (!MWBase::Environment::get().getWindowManager()->isAllowed(GW_Inventory)) + return; // make sure the object is of a type that can be picked up std::string type = object.getTypeName(); if ( (type != typeid(ESM::Apparatus).name()) diff --git a/apps/openmw/mwgui/itemselection.hpp b/apps/openmw/mwgui/itemselection.hpp index d6d19d9e1..c9ec23cfa 100644 --- a/apps/openmw/mwgui/itemselection.hpp +++ b/apps/openmw/mwgui/itemselection.hpp @@ -1,3 +1,6 @@ +#ifndef OPENMW_GAME_MWGUI_ITEMSELECTION_H +#define OPENMW_GAME_MWGUI_ITEMSELECTION_H + #include "container.hpp" namespace MWGui @@ -32,3 +35,5 @@ namespace MWGui }; } + +#endif diff --git a/apps/openmw/mwgui/itemview.cpp b/apps/openmw/mwgui/itemview.cpp index f9a900eba..027c3201f 100644 --- a/apps/openmw/mwgui/itemview.cpp +++ b/apps/openmw/mwgui/itemview.cpp @@ -13,23 +13,19 @@ #include "itemmodel.hpp" -namespace -{ - std::string getCountString(const int count) - { - if (count == 1) - return ""; - if (count > 9999) - return boost::lexical_cast(int(count/1000.f)) + "k"; - else - return boost::lexical_cast(count); - } -} - - namespace MWGui { +std::string ItemView::getCountString(int count) +{ + if (count == 1) + return ""; + if (count > 9999) + return boost::lexical_cast(int(count/1000.f)) + "k"; + else + return boost::lexical_cast(count); +} + ItemView::ItemView() : mModel(NULL) , mScrollView(NULL) diff --git a/apps/openmw/mwgui/itemview.hpp b/apps/openmw/mwgui/itemview.hpp index 17f609f2b..74bc66ea0 100644 --- a/apps/openmw/mwgui/itemview.hpp +++ b/apps/openmw/mwgui/itemview.hpp @@ -30,6 +30,8 @@ namespace MWGui void update(); + static std::string getCountString(int count); + private: virtual void initialiseOverride(); diff --git a/apps/openmw/mwgui/journalbooks.cpp b/apps/openmw/mwgui/journalbooks.cpp index 8caea770e..683fe9208 100644 --- a/apps/openmw/mwgui/journalbooks.cpp +++ b/apps/openmw/mwgui/journalbooks.cpp @@ -2,15 +2,6 @@ namespace { - MWGui::BookTypesetter::Utf8Span to_utf8_span (char const * text) - { - typedef MWGui::BookTypesetter::Utf8Point point; - - point begin = reinterpret_cast (text); - - return MWGui::BookTypesetter::Utf8Span (begin, begin + strlen (text)); - } - const MyGUI::Colour linkHot (0.40f, 0.40f, 0.80f); const MyGUI::Colour linkNormal (0.20f, 0.20f, 0.60f); const MyGUI::Colour linkActive (0.50f, 0.50f, 1.00f); @@ -178,6 +169,15 @@ namespace namespace MWGui { +MWGui::BookTypesetter::Utf8Span to_utf8_span (char const * text) +{ + typedef MWGui::BookTypesetter::Utf8Point point; + + point begin = reinterpret_cast (text); + + return MWGui::BookTypesetter::Utf8Span (begin, begin + strlen (text)); +} + typedef TypesetBook::Ptr book; JournalBooks::JournalBooks (JournalViewModel::Ptr model) : diff --git a/apps/openmw/mwgui/journalbooks.hpp b/apps/openmw/mwgui/journalbooks.hpp index 09d3cf1a8..819bda0fd 100644 --- a/apps/openmw/mwgui/journalbooks.hpp +++ b/apps/openmw/mwgui/journalbooks.hpp @@ -6,6 +6,8 @@ namespace MWGui { + MWGui::BookTypesetter::Utf8Span to_utf8_span (char const * text); + struct JournalBooks { typedef TypesetBook::Ptr Book; diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp index da1992474..4ad260fd9 100644 --- a/apps/openmw/mwgui/mainmenu.cpp +++ b/apps/openmw/mwgui/mainmenu.cpp @@ -1,5 +1,7 @@ #include "mainmenu.hpp" +#include + #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" @@ -20,6 +22,22 @@ namespace MWGui , mButtonBox(0), mWidth (w), mHeight (h) , mSaveGameDialog(NULL) { + getWidget(mVersionText, "VersionText"); + std::stringstream sstream; + sstream << "OpenMW version: " << OPENMW_VERSION; + + // adding info about git hash if availible + std::string rev = OPENMW_VERSION_COMMITHASH; + std::string tag = OPENMW_VERSION_TAGHASH; + if (!rev.empty() && !tag.empty()) + { + rev = rev.substr(0,10); + sstream << "\nrevision: " << rev; + } + + std::string output = sstream.str(); + mVersionText->setCaption(output); + updateMenu(); } diff --git a/apps/openmw/mwgui/mainmenu.hpp b/apps/openmw/mwgui/mainmenu.hpp index 6d52f26d5..722b329de 100644 --- a/apps/openmw/mwgui/mainmenu.hpp +++ b/apps/openmw/mwgui/mainmenu.hpp @@ -1,3 +1,6 @@ +#ifndef OPENMW_GAME_MWGUI_MAINMENU_H +#define OPENMW_GAME_MWGUI_MAINMENU_H + #include #include "imagebutton.hpp" @@ -24,6 +27,7 @@ namespace MWGui private: MyGUI::Widget* mButtonBox; + MyGUI::TextBox* mVersionText; std::map mButtons; @@ -35,3 +39,5 @@ namespace MWGui }; } + +#endif diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index ff13ae1af..4c0faeac1 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -19,28 +19,8 @@ #include "windowmanagerimp.hpp" #include "itemselection.hpp" +#include "spellwindow.hpp" -namespace -{ - bool sortItems(const MWWorld::Ptr& left, const MWWorld::Ptr& right) - { - int cmp = left.getClass().getName(left).compare( - right.getClass().getName(right)); - return cmp < 0; - } - - bool sortSpells(const std::string& left, const std::string& right) - { - const MWWorld::Store &spells = - MWBase::Environment::get().getWorld()->getStore().get(); - - const ESM::Spell* a = spells.find(left); - const ESM::Spell* b = spells.find(right); - - int cmp = a->mName.compare(b->mName); - return cmp < 0; - } -} namespace MWGui { diff --git a/apps/openmw/mwgui/race.cpp b/apps/openmw/mwgui/race.cpp index 3dff1b7e4..499c1e191 100644 --- a/apps/openmw/mwgui/race.cpp +++ b/apps/openmw/mwgui/race.cpp @@ -41,9 +41,13 @@ namespace MWGui getWidget(mPreviewImage, "PreviewImage"); getWidget(mHeadRotate, "HeadRotate"); - mHeadRotate->setScrollRange(50); - mHeadRotate->setScrollPosition(25); - mHeadRotate->setScrollViewPage(10); + + // Mouse wheel step is hardcoded to 50 in MyGUI 3.2 ("FIXME"). + // Give other steps the same value to accomodate. + mHeadRotate->setScrollRange(1000); + mHeadRotate->setScrollPosition(500); + mHeadRotate->setScrollViewPage(50); + mHeadRotate->setScrollPage(50); mHeadRotate->eventScrollChangePosition += MyGUI::newDelegate(this, &RaceDialog::onHeadRotate); // Set up next/previous buttons @@ -171,9 +175,9 @@ namespace MWGui eventBack(); } - void RaceDialog::onHeadRotate(MyGUI::ScrollBar*, size_t _position) + void RaceDialog::onHeadRotate(MyGUI::ScrollBar* scroll, size_t _position) { - float angle = (float(_position) / 49.f - 0.5) * 3.14 * 2; + float angle = (float(_position) / (scroll->getScrollRange()-1) - 0.5) * 3.14 * 2; float diff = angle - mCurrentAngle; mPreview->update (diff); mPreviewDirty = true; diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp index 6b261a799..b052739bd 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -18,8 +18,16 @@ #include "inventorywindow.hpp" #include "confirmationdialog.hpp" -namespace +namespace MWGui { + + bool sortItems(const MWWorld::Ptr& left, const MWWorld::Ptr& right) + { + int cmp = left.getClass().getName(left).compare( + right.getClass().getName(right)); + return cmp < 0; + } + bool sortSpells(const std::string& left, const std::string& right) { const MWWorld::Store &spells = @@ -32,16 +40,6 @@ namespace return cmp < 0; } - bool sortItems(const MWWorld::Ptr& left, const MWWorld::Ptr& right) - { - int cmp = MWWorld::Class::get(left).getName(left).compare( - MWWorld::Class::get(right).getName(right)); - return cmp < 0; - } -} - -namespace MWGui -{ SpellWindow::SpellWindow(DragAndDrop* drag) : WindowPinnableBase("openmw_spell_window.layout") , NoDrop(drag, mMainWidget) diff --git a/apps/openmw/mwgui/spellwindow.hpp b/apps/openmw/mwgui/spellwindow.hpp index 38a761931..53eed1ba1 100644 --- a/apps/openmw/mwgui/spellwindow.hpp +++ b/apps/openmw/mwgui/spellwindow.hpp @@ -2,11 +2,16 @@ #define MWGUI_SPELLWINDOW_H #include "windowpinnablebase.hpp" +#include "../mwworld/ptr.hpp" namespace MWGui { class SpellIcons; + bool sortItems(const MWWorld::Ptr& left, const MWWorld::Ptr& right); + + bool sortSpells(const std::string& left, const std::string& right); + class SpellWindow : public WindowPinnableBase, public NoDrop { public: diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 30cefe2df..4bfd3f465 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -143,6 +143,13 @@ namespace MWInput mControlSwitch["vanitymode"] = true; } + void InputManager::clear() + { + // Enable all controls + for (std::map::iterator it = mControlSwitch.begin(); it != mControlSwitch.end(); ++it) + it->second = true; + } + InputManager::~InputManager() { mInputBinder->save (mUserFile); diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index bd3f4954b..2eab03a34 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -65,6 +65,9 @@ namespace MWInput virtual ~InputManager(); + /// Clear all savegame-specific data + virtual void clear(); + virtual void update(float dt, bool loading); void setPlayer (MWWorld::Player* player) { mPlayer = player; } diff --git a/apps/openmw/mwmechanics/aiactivate.cpp b/apps/openmw/mwmechanics/aiactivate.cpp index bdd7c8f3b..8610cf4b2 100644 --- a/apps/openmw/mwmechanics/aiactivate.cpp +++ b/apps/openmw/mwmechanics/aiactivate.cpp @@ -10,16 +10,6 @@ #include "steering.hpp" #include "movement.hpp" -namespace -{ - float sgn(float a) - { - if(a > 0) - return 1.0; - return -1.0; - } -} - MWMechanics::AiActivate::AiActivate(const std::string &objectId) : mObjectId(objectId) { @@ -38,7 +28,7 @@ bool MWMechanics::AiActivate::execute (const MWWorld::Ptr& actor,float duration) MWWorld::Ptr player = world->getPlayerPtr(); if(cell->mData.mX != player.getCell()->getCell()->mData.mX) { - int sideX = sgn(cell->mData.mX - player.getCell()->getCell()->mData.mX); + int sideX = PathFinder::sgn(cell->mData.mX - player.getCell()->getCell()->mData.mX); //check if actor is near the border of an inactive cell. If so, stop walking. if(sideX * (pos.pos[0] - cell->mData.mX*ESM::Land::REAL_SIZE) > sideX * (ESM::Land::REAL_SIZE/2.0f - 200.0f)) @@ -49,7 +39,7 @@ bool MWMechanics::AiActivate::execute (const MWWorld::Ptr& actor,float duration) } if(cell->mData.mY != player.getCell()->getCell()->mData.mY) { - int sideY = sgn(cell->mData.mY - player.getCell()->getCell()->mData.mY); + int sideY = PathFinder::sgn(cell->mData.mY - player.getCell()->getCell()->mData.mY); //check if actor is near the border of an inactive cell. If so, stop walking. if(sideY * (pos.pos[1] - cell->mData.mY*ESM::Land::REAL_SIZE) > sideY * (ESM::Land::REAL_SIZE/2.0f - 200.0f)) diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index b54f1d39c..f27fada39 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -11,16 +11,6 @@ #include "steering.hpp" #include "movement.hpp" -namespace -{ - float sgn(float a) - { - if(a > 0) - return 1.0; - return -1.0; - } -} - /* TODO: Test vanilla behavior on passing x0, y0, and z0 with duration of anything including 0. TODO: Different behavior for AIEscort a d x y z and AIEscortCell a c d x y z. @@ -91,7 +81,7 @@ namespace MWMechanics if(actor.getCell()->getCell()->mData.mX != player.getCell()->getCell()->mData.mX) { - int sideX = sgn(actor.getCell()->getCell()->mData.mX - player.getCell()->getCell()->mData.mX); + int sideX = PathFinder::sgn(actor.getCell()->getCell()->mData.mX - player.getCell()->getCell()->mData.mX); // Check if actor is near the border of an inactive cell. If so, pause walking. if(sideX * (pos.pos[0] - actor.getCell()->getCell()->mData.mX * ESM::Land::REAL_SIZE) > sideX * (ESM::Land::REAL_SIZE / 2.0 - 200)) @@ -102,7 +92,7 @@ namespace MWMechanics } if(actor.getCell()->getCell()->mData.mY != player.getCell()->getCell()->mData.mY) { - int sideY = sgn(actor.getCell()->getCell()->mData.mY - player.getCell()->getCell()->mData.mY); + int sideY = PathFinder::sgn(actor.getCell()->getCell()->mData.mY - player.getCell()->getCell()->mData.mY); // Check if actor is near the border of an inactive cell. If so, pause walking. if(sideY*(pos.pos[1] - actor.getCell()->getCell()->mData.mY * ESM::Land::REAL_SIZE) > sideY * (ESM::Land::REAL_SIZE / 2.0 - 200)) diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index 1fd719c60..c62c4e970 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -9,16 +9,6 @@ #include "steering.hpp" #include "movement.hpp" -namespace -{ - float sgn(float a) - { - if(a > 0) - return 1.0; - return -1.0; - } -} - namespace MWMechanics { AiTravel::AiTravel(float x, float y, float z) @@ -43,7 +33,7 @@ namespace MWMechanics MWWorld::Ptr player = world->getPlayerPtr(); if(cell->mData.mX != player.getCell()->getCell()->mData.mX) { - int sideX = sgn(cell->mData.mX - player.getCell()->getCell()->mData.mX); + int sideX = PathFinder::sgn(cell->mData.mX - player.getCell()->getCell()->mData.mX); //check if actor is near the border of an inactive cell. If so, stop walking. if(sideX * (pos.pos[0] - cell->mData.mX*ESM::Land::REAL_SIZE) > sideX * (ESM::Land::REAL_SIZE/2.0f - 200.0f)) @@ -54,7 +44,7 @@ namespace MWMechanics } if(cell->mData.mY != player.getCell()->getCell()->mData.mY) { - int sideY = sgn(cell->mData.mY - player.getCell()->getCell()->mData.mY); + int sideY = PathFinder::sgn(cell->mData.mY - player.getCell()->getCell()->mData.mY); //check if actor is near the border of an inactive cell. If so, stop walking. if(sideY * (pos.pos[1] - cell->mData.mY*ESM::Land::REAL_SIZE) > sideY * (ESM::Land::REAL_SIZE/2.0f - 200.0f)) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 9a95822f5..2db875a01 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -15,18 +15,13 @@ #include "steering.hpp" #include "movement.hpp" -namespace -{ - float sgn(float a) - { - if(a > 0) - return 1.0; - return -1.0; - } -} - namespace MWMechanics { + // NOTE: determined empirically but probably need further tweaking + static const int COUNT_BEFORE_STUCK = 20; + static const int COUNT_BEFORE_RESET = 200; + static const int COUNT_EVADE = 7; + AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat): mDistance(distance), mDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle), mRepeat(repeat) , mCellX(std::numeric_limits::max()) @@ -36,6 +31,11 @@ namespace MWMechanics , mX(0) , mY(0) , mZ(0) + , mPrevX(0) + , mPrevY(0) + , mWalkState(State_Norm) + , mStuckCount(0) + , mEvadeCount(0) , mSaidGreeting(false) { for(unsigned short counter = 0; counter < mIdle.size(); counter++) @@ -298,9 +298,91 @@ namespace MWMechanics } else { - zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); + /* 1 n + * State_Norm <---> State_CheckStuck --> State_Evade + * ^ ^ | ^ | ^ | | + * | | | | | | | | + * | +---+ +---+ +---+ | m + * | any < n < m | + * +--------------------------------------------+ + */ + bool samePosition = (abs(pos.pos[0] - mPrevX) < 1) && (abs(pos.pos[1] - mPrevY) < 1); + switch(mWalkState) + { + case State_Norm: + { + if(!samePosition) + break; + else + mWalkState = State_CheckStuck; + } + /* FALL THROUGH */ + case State_CheckStuck: + { + if(!samePosition) + { + mWalkState = State_Norm; + // to do this properly need yet another variable, simply don't clear for now + //mStuckCount = 0; + break; + } + else + { + // consider stuck only if position unchanges consecutively + if((mStuckCount++ % COUNT_BEFORE_STUCK) == 0) + mWalkState = State_Evade; + // NOTE: mStuckCount is purposely not cleared here + else + break; // still in the same state, but counter got incremented + } + } + /* FALL THROUGH */ + case State_Evade: + { + if(mEvadeCount++ < COUNT_EVADE) + break; + else + { + mWalkState = State_Norm; // tried to evade, assume all is ok and start again + // NOTE: mStuckCount is purposely not cleared here + mEvadeCount = 0; + } + } + /* NO DEFAULT CASE */ + } - actor.getClass().getMovementSettings(actor).mPosition[1] = 1; + if(mWalkState == State_Evade) + { + //std::cout << "Stuck \""<= COUNT_BEFORE_RESET) // something has gone wrong, reset + { + //std::cout << "Reset \""< 0) - return 1.0; - return -1.0; - } - int getClosestPoint(const ESM::Pathgrid* grid, float x, float y, float z) { if(!grid || grid->mPoints.empty()) diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 8771ef0ca..ecaaef568 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -4,6 +4,8 @@ #include #include +#include + namespace MWWorld { class CellStore; @@ -16,6 +18,20 @@ namespace MWMechanics public: PathFinder(); + static float sgn(Ogre::Radian a) + { + if(a.valueRadians() > 0) + return 1.0; + return -1.0; + } + + static float sgn(float a) + { + if(a > 0) + return 1.0; + return -1.0; + } + void clearPath(); void buildPathgridGraph(const ESM::Pathgrid* pathGrid); diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 8ef584154..0eb883953 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -55,6 +55,8 @@ CreatureWeaponAnimation::CreatureWeaponAnimation(const MWWorld::Ptr &ptr) updateParts(); } + + mWeaponAnimationTime = Ogre::SharedPtr(new WeaponAnimationTime(this)); } void CreatureWeaponAnimation::showWeapons(bool showWeapon) @@ -110,6 +112,20 @@ void CreatureWeaponAnimation::updatePart(NifOgre::ObjectScenePtr& scene, int slo setRenderProperties(scene, RV_Actors, RQG_Main, RQG_Alpha, 0, !item.getClass().getEnchantment(item).empty(), &glowColor); + // Crossbows start out with a bolt attached + if (slot == MWWorld::InventoryStore::Slot_CarriedRight && + item.getTypeName() == typeid(ESM::Weapon).name() && + item.get()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow) + { + MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); + if (ammo != inv.end() && ammo->get()->mBase->mData.mType == ESM::Weapon::Bolt) + attachArrow(); + else + mAmmunition.setNull(); + } + else + mAmmunition.setNull(); + if(scene->mSkelBase) { Ogre::SkeletonInstance *skel = scene->mSkelBase->getSkeleton(); @@ -133,15 +149,42 @@ void CreatureWeaponAnimation::updatePart(NifOgre::ObjectScenePtr& scene, int slo updateSkeletonInstance(mSkelBase->getSkeleton(), skel); } - // TODO: - // type == ESM::PRT_Weapon should get an animation source based on the current offset - // of the weapon attack animation (from its beginning, or start marker?) std::vector >::iterator ctrl(scene->mControllers.begin()); for(;ctrl != scene->mControllers.end();ctrl++) { if(ctrl->getSource().isNull()) - ctrl->setSource(Ogre::SharedPtr(new NullAnimationTime())); + { + if (slot == MWWorld::InventoryStore::Slot_CarriedRight) + ctrl->setSource(mWeaponAnimationTime); + else + ctrl->setSource(Ogre::SharedPtr(new NullAnimationTime())); + } } } +void CreatureWeaponAnimation::configureAddedObject(NifOgre::ObjectScenePtr object, MWWorld::Ptr ptr, int slot) +{ + Ogre::Vector3 glowColor = getEnchantmentColor(ptr); + + setRenderProperties(object, RV_Actors, RQG_Main, RQG_Alpha, 0, + !ptr.getClass().getEnchantment(ptr).empty(), &glowColor); +} + +void CreatureWeaponAnimation::attachArrow() +{ + WeaponAnimation::attachArrow(mPtr); +} + +void CreatureWeaponAnimation::releaseArrow() +{ + WeaponAnimation::releaseArrow(mPtr); +} + +Ogre::Vector3 CreatureWeaponAnimation::runAnimation(float duration) +{ + Ogre::Vector3 ret = Animation::runAnimation(duration); + pitchSkeleton(mPtr.getRefData().getPosition().rot[0], mSkelBase->getSkeleton()); + return ret; +} + } diff --git a/apps/openmw/mwrender/creatureanimation.hpp b/apps/openmw/mwrender/creatureanimation.hpp index 37826673d..d6cd8a517 100644 --- a/apps/openmw/mwrender/creatureanimation.hpp +++ b/apps/openmw/mwrender/creatureanimation.hpp @@ -2,6 +2,7 @@ #define GAME_RENDER_CREATUREANIMATION_H #include "animation.hpp" +#include "weaponanimation.hpp" #include "../mwworld/inventorystore.hpp" namespace MWWorld @@ -21,7 +22,7 @@ namespace MWRender // For creatures with weapons and shields // Animation is already virtual anyway, so might as well make a separate class. // Most creatures don't need weapons/shields, so this will save some memory. - class CreatureWeaponAnimation : public Animation, public MWWorld::InventoryStoreListener + class CreatureWeaponAnimation : public Animation, public WeaponAnimation, public MWWorld::InventoryStoreListener { public: CreatureWeaponAnimation(const MWWorld::Ptr& ptr); @@ -36,11 +37,29 @@ namespace MWRender void updatePart(NifOgre::ObjectScenePtr& scene, int slot); + virtual void attachArrow(); + virtual void releaseArrow(); + + virtual Ogre::Vector3 runAnimation(float duration); + + /// A relative factor (0-1) that decides if and how much the skeleton should be pitched + /// to indicate the facing orientation of the character. + virtual void setPitchFactor(float factor) { mPitchFactor = factor; } + + virtual void setWeaponGroup(const std::string& group) { mWeaponAnimationTime->setGroup(group); } + + // WeaponAnimation + virtual NifOgre::ObjectScenePtr getWeapon() { return mWeapon; } + virtual void showWeapon(bool show) { showWeapons(show); } + virtual void configureAddedObject(NifOgre::ObjectScenePtr object, MWWorld::Ptr ptr, int slot); + private: NifOgre::ObjectScenePtr mWeapon; NifOgre::ObjectScenePtr mShield; bool mShowWeapons; bool mShowCarriedLeft; + + Ogre::SharedPtr mWeaponAnimationTime; }; } diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 8f8e65cd0..a09a58191 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -76,27 +76,6 @@ float HeadAnimationTime::getValue() const return 1; } -float WeaponAnimationTime::getValue() const -{ - if (mWeaponGroup.empty()) - return 0; - float current = mAnimation->getCurrentTime(mWeaponGroup); - if (current == -1) - return 0; - return current - mStartTime; -} - -void WeaponAnimationTime::setGroup(const std::string &group) -{ - mWeaponGroup = group; - mStartTime = mAnimation->getStartTime(mWeaponGroup); -} - -void WeaponAnimationTime::updateStartTime() -{ - setGroup(mWeaponGroup); -} - static NpcAnimation::PartBoneMap createPartListMap() { NpcAnimation::PartBoneMap result; @@ -147,8 +126,7 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, int v mShowCarriedLeft(true), mFirstPersonOffset(0.f, 0.f, 0.f), mAlpha(1.f), - mNpcType(Type_Normal), - mPitchFactor(0) + mNpcType(Type_Normal) { mNpc = mPtr.get()->mBase; @@ -538,14 +516,10 @@ Ogre::Vector3 NpcAnimation::runAnimation(float timepassed) // updateSkeletonInstance, below, touches the hands. node->translate(mFirstPersonOffset, Ogre::Node::TS_WORLD); } - else if (mPitchFactor > 0) + else { // In third person mode we may still need pitch for ranged weapon targeting - float pitch = mPtr.getRefData().getPosition().rot[0] * mPitchFactor; - Ogre::Node *node = baseinst->getBone("Bip01 Spine2"); - node->pitch(Ogre::Radian(-pitch/2), Ogre::Node::TS_WORLD); - node = baseinst->getBone("Bip01 Spine1"); - node->pitch(Ogre::Radian(-pitch/2), Ogre::Node::TS_WORLD); + pitchSkeleton(mPtr.getRefData().getPosition().rot[0], baseinst); } mFirstPersonOffset = 0.f; // reset the X, Y, Z offset for the next frame. @@ -695,13 +669,14 @@ void NpcAnimation::showWeapons(bool showWeapon) { MWWorld::InventoryStore &inv = MWWorld::Class::get(mPtr).getInventoryStore(mPtr); MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if(weapon != inv.end()) // special case for weapons + if(weapon != inv.end()) { Ogre::Vector3 glowColor = getEnchantmentColor(*weapon); std::string mesh = MWWorld::Class::get(*weapon).getModel(*weapon); addOrReplaceIndividualPart(ESM::PRT_Weapon, MWWorld::InventoryStore::Slot_CarriedRight, 1, mesh, !weapon->getClass().getEnchantment(*weapon).empty(), &glowColor); + // Crossbows start out with a bolt attached if (weapon->getTypeName() == typeid(ESM::Weapon).name() && weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow) { @@ -743,113 +718,24 @@ void NpcAnimation::showCarriedLeft(bool show) removeIndividualPart(ESM::PRT_Shield); } +void NpcAnimation::configureAddedObject(NifOgre::ObjectScenePtr object, MWWorld::Ptr ptr, int slot) +{ + Ogre::Vector3 glowColor = getEnchantmentColor(ptr); + setRenderProperties(object, (mViewMode == VM_FirstPerson) ? RV_FirstPerson : mVisibilityFlags, RQG_Main, RQG_Alpha, 0, + !ptr.getClass().getEnchantment(ptr).empty(), &glowColor); + + std::for_each(object->mEntities.begin(), object->mEntities.end(), SetObjectGroup(slot)); + std::for_each(object->mParticles.begin(), object->mParticles.end(), SetObjectGroup(slot)); +} + void NpcAnimation::attachArrow() { - MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); - MWWorld::ContainerStoreIterator weaponSlot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if (weaponSlot != inv.end() && weaponSlot->get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown) - showWeapons(true); - else - { - NifOgre::ObjectScenePtr weapon = mObjectParts[ESM::PRT_Weapon]; - - MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); - if (ammo == inv.end()) - return; - std::string model = ammo->getClass().getModel(*ammo); - - mAmmunition = NifOgre::Loader::createObjects(weapon->mSkelBase, "ArrowBone", mInsert, model); - Ogre::Vector3 glowColor = getEnchantmentColor(*ammo); - setRenderProperties(mAmmunition, (mViewMode == VM_FirstPerson) ? RV_FirstPerson : mVisibilityFlags, RQG_Main, RQG_Alpha, 0, - !ammo->getClass().getEnchantment(*ammo).empty(), &glowColor); - - std::for_each(mAmmunition->mEntities.begin(), mAmmunition->mEntities.end(), SetObjectGroup(MWWorld::InventoryStore::Slot_Ammunition)); - std::for_each(mAmmunition->mParticles.begin(), mAmmunition->mParticles.end(), SetObjectGroup(MWWorld::InventoryStore::Slot_Ammunition)); - } + WeaponAnimation::attachArrow(mPtr); } void NpcAnimation::releaseArrow() { - MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); - MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if (weapon == inv.end()) - return; - - // The orientation of the launched projectile. Always the same as the actor orientation, even if the ArrowBone's orientation dictates otherwise. - Ogre::Quaternion orient = Ogre::Quaternion(Ogre::Radian(mPtr.getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * - Ogre::Quaternion(Ogre::Radian(mPtr.getRefData().getPosition().rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X); - - const MWWorld::Store &gmst = - MWBase::Environment::get().getWorld()->getStore().get(); - - // Reduce fatigue - // somewhat of a guess, but using the weapon weight makes sense - const float fFatigueAttackBase = gmst.find("fFatigueAttackBase")->getFloat(); - const float fFatigueAttackMult = gmst.find("fFatigueAttackMult")->getFloat(); - const float fWeaponFatigueMult = gmst.find("fWeaponFatigueMult")->getFloat(); - MWMechanics::CreatureStats& attackerStats = mPtr.getClass().getCreatureStats(mPtr); - MWMechanics::DynamicStat fatigue = attackerStats.getFatigue(); - const float normalizedEncumbrance = mPtr.getClass().getEncumbrance(mPtr) / mPtr.getClass().getCapacity(mPtr); - float fatigueLoss = fFatigueAttackBase + normalizedEncumbrance * fFatigueAttackMult; - if (!weapon->isEmpty()) - fatigueLoss += weapon->getClass().getWeight(*weapon) * attackerStats.getAttackStrength() * fWeaponFatigueMult; - fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss); - attackerStats.setFatigue(fatigue); - - if (weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown) - { - // Thrown weapons get detached now - NifOgre::ObjectScenePtr objects = mObjectParts[ESM::PRT_Weapon]; - - Ogre::Vector3 launchPos(0,0,0); - if (objects->mSkelBase) - { - launchPos = objects->mSkelBase->getParentNode()->_getDerivedPosition(); - } - else if (objects->mEntities.size()) - { - objects->mEntities[0]->getParentNode()->needUpdate(true); - launchPos = objects->mEntities[0]->getParentNode()->_getDerivedPosition(); - } - - float fThrownWeaponMinSpeed = gmst.find("fThrownWeaponMinSpeed")->getFloat(); - float fThrownWeaponMaxSpeed = gmst.find("fThrownWeaponMaxSpeed")->getFloat(); - float speed = fThrownWeaponMinSpeed + (fThrownWeaponMaxSpeed - fThrownWeaponMinSpeed) * - mPtr.getClass().getCreatureStats(mPtr).getAttackStrength(); - - MWBase::Environment::get().getWorld()->launchProjectile(mPtr, *weapon, launchPos, orient, *weapon, speed); - - showWeapons(false); - - inv.remove(*weapon, 1, mPtr); - } - else - { - // With bows and crossbows only the used arrow/bolt gets detached - MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); - if (ammo == inv.end()) - return; - - Ogre::Vector3 launchPos(0,0,0); - if (mAmmunition->mSkelBase) - { - launchPos = mAmmunition->mSkelBase->getParentNode()->_getDerivedPosition(); - } - else if (mAmmunition->mEntities.size()) - { - mAmmunition->mEntities[0]->getParentNode()->needUpdate(true); - launchPos = mAmmunition->mEntities[0]->getParentNode()->_getDerivedPosition(); - } - - float fProjectileMinSpeed = gmst.find("fProjectileMinSpeed")->getFloat(); - float fProjectileMaxSpeed = gmst.find("fProjectileMaxSpeed")->getFloat(); - float speed = fProjectileMinSpeed + (fProjectileMaxSpeed - fProjectileMinSpeed) * mPtr.getClass().getCreatureStats(mPtr).getAttackStrength(); - - MWBase::Environment::get().getWorld()->launchProjectile(mPtr, *ammo, launchPos, orient, *weapon, speed); - - inv.remove(*ammo, 1, mPtr); - mAmmunition.setNull(); - } + WeaponAnimation::releaseArrow(mPtr); } void NpcAnimation::permanentEffectAdded(const ESM::MagicEffect *magicEffect, bool isNew, bool playSound) diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 725fde01d..8ec46facd 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -5,6 +5,8 @@ #include "../mwworld/inventorystore.hpp" +#include "weaponanimation.hpp" + namespace ESM { struct NPC; @@ -25,24 +27,7 @@ public: { } }; -class WeaponAnimationTime : public Ogre::ControllerValue -{ -private: - Animation* mAnimation; - std::string mWeaponGroup; - float mStartTime; -public: - WeaponAnimationTime(Animation* animation) : mAnimation(animation), mStartTime(0) {} - void setGroup(const std::string& group); - void updateStartTime(); - - virtual Ogre::Real getValue() const; - virtual void setValue(Ogre::Real value) - { } -}; - - -class NpcAnimation : public Animation, public MWWorld::InventoryStoreListener +class NpcAnimation : public Animation, public WeaponAnimation, public MWWorld::InventoryStoreListener { public: virtual void equipmentChanged() { updateParts(); } @@ -91,7 +76,6 @@ private: Ogre::SharedPtr mWeaponAnimationTime; float mAlpha; - float mPitchFactor; void updateNpcBase(); @@ -138,7 +122,10 @@ public: virtual void attachArrow(); virtual void releaseArrow(); - NifOgre::ObjectScenePtr mAmmunition; + // WeaponAnimation + virtual NifOgre::ObjectScenePtr getWeapon() { return mObjectParts[ESM::PRT_Weapon]; } + virtual void showWeapon(bool show) { showWeapons(show); } + virtual void configureAddedObject(NifOgre::ObjectScenePtr object, MWWorld::Ptr ptr, int slot); void setViewMode(ViewMode viewMode); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index a0d06f068..15d56b8a9 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -1073,7 +1073,7 @@ float RenderingManager::getCameraDistance() const void RenderingManager::spawnEffect(const std::string &model, const std::string &texture, const Vector3 &worldPosition, float scale) { - mEffectManager->addEffect(model, "", worldPosition, scale); + mEffectManager->addEffect(model, texture, worldPosition, scale); } } // namespace diff --git a/apps/openmw/mwrender/weaponanimation.cpp b/apps/openmw/mwrender/weaponanimation.cpp new file mode 100644 index 000000000..5f953bd83 --- /dev/null +++ b/apps/openmw/mwrender/weaponanimation.cpp @@ -0,0 +1,159 @@ +#include "weaponanimation.hpp" + +#include +#include +#include +#include + +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" + +#include "../mwworld/inventorystore.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" + +#include "../mwmechanics/creaturestats.hpp" + +#include "animation.hpp" + +namespace MWRender +{ + +float WeaponAnimationTime::getValue() const +{ + if (mWeaponGroup.empty()) + return 0; + float current = mAnimation->getCurrentTime(mWeaponGroup); + if (current == -1) + return 0; + return current - mStartTime; +} + +void WeaponAnimationTime::setGroup(const std::string &group) +{ + mWeaponGroup = group; + mStartTime = mAnimation->getStartTime(mWeaponGroup); +} + +void WeaponAnimationTime::updateStartTime() +{ + setGroup(mWeaponGroup); +} + +void WeaponAnimation::attachArrow(MWWorld::Ptr actor) +{ + MWWorld::InventoryStore& inv = actor.getClass().getInventoryStore(actor); + MWWorld::ContainerStoreIterator weaponSlot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (weaponSlot != inv.end() && weaponSlot->get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown) + showWeapon(true); + else + { + NifOgre::ObjectScenePtr weapon = getWeapon(); + + MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); + if (ammo == inv.end()) + return; + std::string model = ammo->getClass().getModel(*ammo); + + mAmmunition = NifOgre::Loader::createObjects(weapon->mSkelBase, "ArrowBone", weapon->mSkelBase->getParentSceneNode(), model); + configureAddedObject(mAmmunition, *ammo, MWWorld::InventoryStore::Slot_Ammunition); + } +} + +void WeaponAnimation::releaseArrow(MWWorld::Ptr actor) +{ + MWWorld::InventoryStore& inv = actor.getClass().getInventoryStore(actor); + MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (weapon == inv.end()) + return; + + // The orientation of the launched projectile. Always the same as the actor orientation, even if the ArrowBone's orientation dictates otherwise. + Ogre::Quaternion orient = Ogre::Quaternion(Ogre::Radian(actor.getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * + Ogre::Quaternion(Ogre::Radian(actor.getRefData().getPosition().rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X); + + const MWWorld::Store &gmst = + MWBase::Environment::get().getWorld()->getStore().get(); + + // Reduce fatigue + // somewhat of a guess, but using the weapon weight makes sense + const float fFatigueAttackBase = gmst.find("fFatigueAttackBase")->getFloat(); + const float fFatigueAttackMult = gmst.find("fFatigueAttackMult")->getFloat(); + const float fWeaponFatigueMult = gmst.find("fWeaponFatigueMult")->getFloat(); + MWMechanics::CreatureStats& attackerStats = actor.getClass().getCreatureStats(actor); + MWMechanics::DynamicStat fatigue = attackerStats.getFatigue(); + const float normalizedEncumbrance = actor.getClass().getEncumbrance(actor) / actor.getClass().getCapacity(actor); + float fatigueLoss = fFatigueAttackBase + normalizedEncumbrance * fFatigueAttackMult; + if (!weapon->isEmpty()) + fatigueLoss += weapon->getClass().getWeight(*weapon) * attackerStats.getAttackStrength() * fWeaponFatigueMult; + fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss); + attackerStats.setFatigue(fatigue); + + if (weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown) + { + // Thrown weapons get detached now + NifOgre::ObjectScenePtr objects = getWeapon(); + + Ogre::Vector3 launchPos(0,0,0); + if (objects->mSkelBase) + { + launchPos = objects->mSkelBase->getParentNode()->_getDerivedPosition(); + } + else if (objects->mEntities.size()) + { + objects->mEntities[0]->getParentNode()->needUpdate(true); + launchPos = objects->mEntities[0]->getParentNode()->_getDerivedPosition(); + } + + float fThrownWeaponMinSpeed = gmst.find("fThrownWeaponMinSpeed")->getFloat(); + float fThrownWeaponMaxSpeed = gmst.find("fThrownWeaponMaxSpeed")->getFloat(); + float speed = fThrownWeaponMinSpeed + (fThrownWeaponMaxSpeed - fThrownWeaponMinSpeed) * + actor.getClass().getCreatureStats(actor).getAttackStrength(); + + MWBase::Environment::get().getWorld()->launchProjectile(actor, *weapon, launchPos, orient, *weapon, speed); + + showWeapon(false); + + inv.remove(*weapon, 1, actor); + } + else + { + // With bows and crossbows only the used arrow/bolt gets detached + MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); + if (ammo == inv.end()) + return; + + Ogre::Vector3 launchPos(0,0,0); + if (mAmmunition->mSkelBase) + { + launchPos = mAmmunition->mSkelBase->getParentNode()->_getDerivedPosition(); + } + else if (mAmmunition->mEntities.size()) + { + mAmmunition->mEntities[0]->getParentNode()->needUpdate(true); + launchPos = mAmmunition->mEntities[0]->getParentNode()->_getDerivedPosition(); + } + + float fProjectileMinSpeed = gmst.find("fProjectileMinSpeed")->getFloat(); + float fProjectileMaxSpeed = gmst.find("fProjectileMaxSpeed")->getFloat(); + float speed = fProjectileMinSpeed + (fProjectileMaxSpeed - fProjectileMinSpeed) * actor.getClass().getCreatureStats(actor).getAttackStrength(); + + MWBase::Environment::get().getWorld()->launchProjectile(actor, *ammo, launchPos, orient, *weapon, speed); + + inv.remove(*ammo, 1, actor); + mAmmunition.setNull(); + } +} + +void WeaponAnimation::pitchSkeleton(float xrot, Ogre::SkeletonInstance* skel) +{ + if (mPitchFactor == 0) + return; + + float pitch = xrot * mPitchFactor; + Ogre::Node *node = skel->getBone("Bip01 Spine2"); + node->pitch(Ogre::Radian(-pitch/2), Ogre::Node::TS_WORLD); + node = skel->getBone("Bip01 Spine1"); + node->pitch(Ogre::Radian(-pitch/2), Ogre::Node::TS_WORLD); +} + +} diff --git a/apps/openmw/mwrender/weaponanimation.hpp b/apps/openmw/mwrender/weaponanimation.hpp new file mode 100644 index 000000000..c09aa65d9 --- /dev/null +++ b/apps/openmw/mwrender/weaponanimation.hpp @@ -0,0 +1,56 @@ +#ifndef OPENMW_MWRENDER_WEAPONANIMATION_H +#define OPENMW_MWRENDER_WEAPONANIMATION_H + +#include + +#include + +#include "../mwworld/ptr.hpp" + +namespace MWRender +{ + + class Animation; + + class WeaponAnimationTime : public Ogre::ControllerValue + { + private: + Animation* mAnimation; + std::string mWeaponGroup; + float mStartTime; + public: + WeaponAnimationTime(Animation* animation) : mAnimation(animation), mStartTime(0) {} + void setGroup(const std::string& group); + void updateStartTime(); + + virtual Ogre::Real getValue() const; + virtual void setValue(Ogre::Real value) + { } + }; + + /// Handles attach & release of projectiles for ranged weapons + class WeaponAnimation + { + public: + WeaponAnimation() : mPitchFactor(0) {} + + virtual void attachArrow(MWWorld::Ptr actor); + virtual void releaseArrow(MWWorld::Ptr actor); + + protected: + NifOgre::ObjectScenePtr mAmmunition; + + virtual NifOgre::ObjectScenePtr getWeapon() = 0; + virtual void showWeapon(bool show) = 0; + virtual void configureAddedObject(NifOgre::ObjectScenePtr object, MWWorld::Ptr ptr, int slot) = 0; + + /// A relative factor (0-1) that decides if and how much the skeleton should be pitched + /// to indicate the facing orientation of the character, for ranged weapon aiming. + float mPitchFactor; + + void pitchSkeleton(float xrot, Ogre::SkeletonInstance* skel); + }; + +} + +#endif diff --git a/apps/openmw/mwscript/containerextensions.cpp b/apps/openmw/mwscript/containerextensions.cpp index d489bfaf1..66c8d4468 100644 --- a/apps/openmw/mwscript/containerextensions.cpp +++ b/apps/openmw/mwscript/containerextensions.cpp @@ -121,7 +121,7 @@ namespace MWScript std::string itemName; for (MWWorld::ContainerStoreIterator iter(store.begin()); iter != store.end(); ++iter) - if (Misc::StringUtils::ciEqual(iter->getCellRef().mRefID, item)) + if (::Misc::StringUtils::ciEqual(iter->getCellRef().mRefID, item)) itemName = iter->getClass().getName(*iter); int numRemoved = store.remove(item, count, ptr); @@ -165,7 +165,7 @@ namespace MWScript MWWorld::ContainerStoreIterator it = invStore.begin(); for (; it != invStore.end(); ++it) { - if (Misc::StringUtils::ciEqual(it->getCellRef().mRefID, item)) + if (::Misc::StringUtils::ciEqual(it->getCellRef().mRefID, item)) break; } if (it == invStore.end()) @@ -268,7 +268,7 @@ namespace MWScript for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) { MWWorld::ContainerStoreIterator it = invStore.getSlot (slot); - if (it != invStore.end() && Misc::StringUtils::ciEqual(it->getCellRef().mRefID, item)) + if (it != invStore.end() && ::Misc::StringUtils::ciEqual(it->getCellRef().mRefID, item)) { runtime.push(1); return; @@ -295,7 +295,7 @@ namespace MWScript it != invStore.end(); ++it) { - if (Misc::StringUtils::ciEqual(it->getCellRef().mSoul, name)) + if (::Misc::StringUtils::ciEqual(it->getCellRef().mSoul, name)) { runtime.push(1); return; diff --git a/apps/openmw/mwscript/globalscripts.cpp b/apps/openmw/mwscript/globalscripts.cpp index 179e2bb0b..527c576cc 100644 --- a/apps/openmw/mwscript/globalscripts.cpp +++ b/apps/openmw/mwscript/globalscripts.cpp @@ -24,7 +24,7 @@ namespace MWScript void GlobalScripts::addScript (const std::string& name) { std::map >::iterator iter = - mScripts.find (Misc::StringUtils::lowerCase (name)); + mScripts.find (::Misc::StringUtils::lowerCase (name)); if (iter==mScripts.end()) { @@ -44,7 +44,7 @@ namespace MWScript void GlobalScripts::removeScript (const std::string& name) { std::map >::iterator iter = - mScripts.find (Misc::StringUtils::lowerCase (name)); + mScripts.find (::Misc::StringUtils::lowerCase (name)); if (iter!=mScripts.end()) iter->second.first = false; @@ -53,7 +53,7 @@ namespace MWScript bool GlobalScripts::isRunning (const std::string& name) const { std::map >::const_iterator iter = - mScripts.find (Misc::StringUtils::lowerCase (name)); + mScripts.find (::Misc::StringUtils::lowerCase (name)); if (iter==mScripts.end()) return false; @@ -151,7 +151,7 @@ namespace MWScript Locals& GlobalScripts::getLocals (const std::string& name) { - std::string name2 = Misc::StringUtils::lowerCase (name); + std::string name2 = ::Misc::StringUtils::lowerCase (name); std::map >::iterator iter = mScripts.find (name2); diff --git a/apps/openmw/mwscript/guiextensions.cpp b/apps/openmw/mwscript/guiextensions.cpp index ab8901881..57fc2d470 100644 --- a/apps/openmw/mwscript/guiextensions.cpp +++ b/apps/openmw/mwscript/guiextensions.cpp @@ -113,7 +113,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { std::string cell = (runtime.getStringLiteral (runtime[0].mInteger)); - Misc::StringUtils::toLower(cell); + ::Misc::StringUtils::toLower(cell); runtime.pop(); // "Will match complete or partial cells, so ShowMap, "Vivec" will show cells Vivec and Vivec, Fred's House as well." @@ -126,7 +126,7 @@ namespace MWScript for (; it != cells.extEnd(); ++it) { std::string name = it->mName; - Misc::StringUtils::toLower(name); + ::Misc::StringUtils::toLower(name); if (name.find(cell) != std::string::npos) MWBase::Environment::get().getWindowManager()->addVisitedLocation ( it->mName, diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index c4f672dc7..80467f58a 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -540,7 +540,7 @@ namespace MWScript factionID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); } - Misc::StringUtils::toLower(factionID); + ::Misc::StringUtils::toLower(factionID); if(factionID != "") { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); @@ -569,7 +569,7 @@ namespace MWScript factionID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); } - Misc::StringUtils::toLower(factionID); + ::Misc::StringUtils::toLower(factionID); if(factionID != "") { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); @@ -602,7 +602,7 @@ namespace MWScript factionID = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); } - Misc::StringUtils::toLower(factionID); + ::Misc::StringUtils::toLower(factionID); if(factionID != "") { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); @@ -640,7 +640,7 @@ namespace MWScript factionID = MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().begin()->first; } } - Misc::StringUtils::toLower(factionID); + ::Misc::StringUtils::toLower(factionID); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); if(factionID!="") { @@ -742,7 +742,7 @@ namespace MWScript if (factionId.empty()) throw std::runtime_error ("failed to determine faction"); - Misc::StringUtils::toLower (factionId); + ::Misc::StringUtils::toLower (factionId); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); runtime.push ( @@ -778,7 +778,7 @@ namespace MWScript if (factionId.empty()) throw std::runtime_error ("failed to determine faction"); - Misc::StringUtils::toLower (factionId); + ::Misc::StringUtils::toLower (factionId); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWWorld::Class::get (player).getNpcStats (player).setFactionReputation (factionId, value); @@ -813,7 +813,7 @@ namespace MWScript if (factionId.empty()) throw std::runtime_error ("failed to determine faction"); - Misc::StringUtils::toLower (factionId); + ::Misc::StringUtils::toLower (factionId); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWWorld::Class::get (player).getNpcStats (player).setFactionReputation (factionId, @@ -858,11 +858,11 @@ namespace MWScript MWWorld::Ptr ptr = R()(runtime); std::string race = runtime.getStringLiteral(runtime[0].mInteger); - Misc::StringUtils::toLower(race); + ::Misc::StringUtils::toLower(race); runtime.pop(); std::string npcRace = ptr.get()->mBase->mRace; - Misc::StringUtils::toLower(npcRace); + ::Misc::StringUtils::toLower(npcRace); runtime.push (npcRace == race); } @@ -906,7 +906,7 @@ namespace MWScript factionID = MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().begin()->first; } } - Misc::StringUtils::toLower(factionID); + ::Misc::StringUtils::toLower(factionID); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); if(factionID!="") { diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 9a9175f19..1efc79643 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -99,7 +99,7 @@ namespace MWScript MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,angle); } else - throw std::runtime_error ("invalid ration axis: " + axis); + throw std::runtime_error ("invalid rotation axis: " + axis); } }; @@ -128,7 +128,7 @@ namespace MWScript runtime.push(Ogre::Radian(ptr.getCellRef().mPos.rot[2]).valueDegrees()); } else - throw std::runtime_error ("invalid ration axis: " + axis); + throw std::runtime_error ("invalid rotation axis: " + axis); } }; @@ -157,7 +157,7 @@ namespace MWScript runtime.push(Ogre::Radian(ptr.getRefData().getPosition().rot[2]).valueDegrees()); } else - throw std::runtime_error ("invalid ration axis: " + axis); + throw std::runtime_error ("invalid rotation axis: " + axis); } }; @@ -186,7 +186,7 @@ namespace MWScript runtime.push(ptr.getRefData().getPosition().pos[2]); } else - throw std::runtime_error ("invalid rotation axis: " + axis); + throw std::runtime_error ("invalid axis: " + axis); } }; diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index c83697442..c595de5ae 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -11,9 +11,10 @@ namespace MWSound { -static void fail(const std::string &msg) -{ throw std::runtime_error("FFmpeg exception: "+msg); } - +void FFmpeg_Decoder::fail(const std::string &msg) +{ + throw std::runtime_error("FFmpeg exception: "+msg); +} int FFmpeg_Decoder::readPacket(void *user_data, uint8_t *buf, int buf_size) { diff --git a/apps/openmw/mwsound/ffmpeg_decoder.hpp b/apps/openmw/mwsound/ffmpeg_decoder.hpp index d0d73379d..8276b45c7 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.hpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.hpp @@ -61,6 +61,8 @@ namespace MWSound virtual void rewind(); virtual size_t getSampleOffset(); + void fail(const std::string &msg); + FFmpeg_Decoder& operator=(const FFmpeg_Decoder &rhs); FFmpeg_Decoder(const FFmpeg_Decoder &rhs); diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 8b01bacdf..d6309c1c9 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -20,6 +20,7 @@ #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/scriptmanager.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwbase/inputmanager.hpp" #include "../mwworld/player.hpp" #include "../mwworld/class.hpp" @@ -39,6 +40,7 @@ void MWState::StateManager::cleanup (bool force) MWBase::Environment::get().getScriptManager()->getGlobalScripts().clear(); MWBase::Environment::get().getWorld()->clear(); MWBase::Environment::get().getWindowManager()->clear(); + MWBase::Environment::get().getInputManager()->clear(); mState = State_NoGame; mCharacterManager.clearCurrentCharacter(); @@ -123,11 +125,10 @@ void MWState::StateManager::newGame (bool bypass) { cleanup(); + MWBase::Environment::get().getWorld()->startNewGame (bypass); + if (!bypass) - { - MWBase::Environment::get().getWorld()->startNewGame(); MWBase::Environment::get().getWindowManager()->setNewGame (true); - } else MWBase::Environment::get().getWorld()->setGlobalInt ("chargenstate", -1); diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 2110086d3..39d48f95b 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -363,7 +363,22 @@ namespace MWWorld return newPtr; } - bool Class::isFlying(const Ptr &ptr) const + bool Class::isBipedal(const Ptr &ptr) const + { + return false; + } + + bool Class::canFly(const Ptr &ptr) const + { + return false; + } + + bool Class::canSwim(const Ptr &ptr) const + { + return false; + } + + bool Class::canWalk(const Ptr &ptr) const { return false; } diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index ad2cc3af4..739fd5942 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -307,7 +307,10 @@ namespace MWWorld return false; } - virtual bool isFlying(const MWWorld::Ptr& ptr) const; + virtual bool isBipedal(const MWWorld::Ptr& ptr) const; + virtual bool canFly(const MWWorld::Ptr& ptr) const; + virtual bool canSwim(const MWWorld::Ptr& ptr) const; + virtual bool canWalk(const MWWorld::Ptr& ptr) const; virtual int getSkill(const MWWorld::Ptr& ptr, int skill) const; diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index 1657cf267..3c0c3ffaa 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -130,31 +130,52 @@ namespace MWWorld position.z += halfExtents.z; waterlevel -= halfExtents.z * 0.5; + /* + * A 3/4 submerged example + * + * +---+ + * | | + * | | <- (original waterlevel) + * | | + * | | <- position <- waterlevel + * | | + * | | + * | | + * +---+ <- (original position) + */ OEngine::Physic::ActorTracer tracer; bool wasOnGround = false; bool isOnGround = false; Ogre::Vector3 inertia(0.0f); Ogre::Vector3 velocity; - if(position.z < waterlevel || isFlying) + + bool canWalk = ptr.getClass().canWalk(ptr); + bool isBipedal = ptr.getClass().isBipedal(ptr); + bool isNpc = ptr.getClass().isNpc(); + + if(position.z < waterlevel || isFlying) // under water by 3/4 or can fly { + // TODO: Shouldn't water have higher drag in calculating velocity? velocity = (Ogre::Quaternion(Ogre::Radian(refpos.rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z)* Ogre::Quaternion(Ogre::Radian(refpos.rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X)) * movement; } else { velocity = Ogre::Quaternion(Ogre::Radian(refpos.rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * movement; + // not in water nor can fly, so need to deal with gravity if(!physicActor->getOnGround()) { // If falling, add part of the incoming velocity with the current inertia - velocity = velocity*time + physicActor->getInertialForce(); + velocity = velocity * time + physicActor->getInertialForce(); } - inertia = velocity; + inertia = velocity; // REM velocity is for z axis only in this code block if(!(movement.z > 0.0f)) { wasOnGround = physicActor->getOnGround(); - tracer.doTrace(colobj, position, position-Ogre::Vector3(0,0,2), engine); + // TODO: Find out if there is a significance with the value 2 used here + tracer.doTrace(colobj, position, position - Ogre::Vector3(0,0,2), engine); if(tracer.mFraction < 1.0f && getSlope(tracer.mPlaneNormal) <= sMaxSlope) isOnGround = true; } @@ -163,24 +184,38 @@ namespace MWWorld if(isOnGround) { // if we're on the ground, don't try to fall - velocity.z = std::max(0.0f, velocity.z); + velocity.z = std::max(0.0f, velocity.z); // NOTE: two different velocity assignments above } Ogre::Vector3 newPosition = position; + /* + * A loop to find newPosition using tracer, if successful different from the starting position. + * nextpos is the local variable used to find potential newPosition, using velocity and remainingTime + * The initial velocity was set earlier (see above). + */ float remainingTime = time; - for(int iterations = 0;iterations < sMaxIterations && remainingTime > 0.01f;++iterations) + for(int iterations = 0; iterations < sMaxIterations && remainingTime > 0.01f; ++iterations) { - Ogre::Vector3 nextpos = newPosition + velocity*remainingTime; + Ogre::Vector3 nextpos = newPosition + velocity * remainingTime; - if(newPosition.z < waterlevel && !isFlying && - nextpos.z > waterlevel && newPosition.z <= waterlevel) + // If not able to fly, walk or bipedal don't allow to move out of water + // TODO: this if condition may not work for large creatures or situations + // where the creature gets above the waterline for some reason + if(newPosition.z < waterlevel && // started 3/4 under water + !isFlying && // can't fly + !canWalk && // can't walk + !isBipedal && // not bipedal (assume bipedals can walk) + !isNpc && // FIXME: shouldn't really need this + nextpos.z > waterlevel && // but about to go above water + newPosition.z <= waterlevel) { const Ogre::Vector3 down(0,0,-1); Ogre::Real movelen = velocity.normalise(); Ogre::Vector3 reflectdir = velocity.reflect(down); reflectdir.normalise(); velocity = slide(reflectdir, down)*movelen; - continue; + // NOTE: remainingTime is unchanged before the loop continues + continue; // velocity updated, calculate nextpos again } // trace to where character would go if there were no obstructions @@ -189,13 +224,14 @@ namespace MWWorld // check for obstructions if(tracer.mFraction >= 1.0f) { - newPosition = tracer.mEndPos; - remainingTime *= (1.0f-tracer.mFraction); + newPosition = tracer.mEndPos; // ok to move, so set newPosition + remainingTime *= (1.0f-tracer.mFraction); // FIXME: remainingTime is no longer used so don't set it? break; } // We hit something. Try to step up onto it. - if(stepMove(colobj, newPosition, velocity, remainingTime, engine)) + // NOTE: May need to stop slaughterfish step out of the water. + if((canWalk || isBipedal || isNpc) && stepMove(colobj, newPosition, velocity, remainingTime, engine)) isOnGround = !(newPosition.z < waterlevel || isFlying); // Only on the ground if there's gravity else { @@ -214,7 +250,7 @@ namespace MWWorld if(isOnGround || wasOnGround) { - tracer.doTrace(colobj, newPosition, newPosition-Ogre::Vector3(0,0,sStepSize+2.0f), engine); + tracer.doTrace(colobj, newPosition, newPosition - Ogre::Vector3(0,0,sStepSize+2.0f), engine); if(tracer.mFraction < 1.0f && getSlope(tracer.mPlaneNormal) <= sMaxSlope) { newPosition.z = tracer.mEndPos.z + 1.0f; @@ -236,7 +272,7 @@ namespace MWWorld } physicActor->setOnGround(isOnGround); - newPosition.z -= halfExtents.z; + newPosition.z -= halfExtents.z; // remove what was added at the beggining return newPosition; } }; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 62ef09ae4..62bdd38ea 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -121,13 +121,15 @@ namespace MWWorld const Files::Collections& fileCollections, const std::vector& contentFiles, const boost::filesystem::path& resDir, const boost::filesystem::path& cacheDir, - ToUTF8::Utf8Encoder* encoder, const std::map& fallbackMap, int mActivationDistanceOverride) + ToUTF8::Utf8Encoder* encoder, const std::map& fallbackMap, + int activationDistanceOverride, const std::string& startCell) : mPlayer (0), mLocalScripts (mStore), mSky (true), mCells (mStore, mEsm), - mActivationDistanceOverride (mActivationDistanceOverride), + mActivationDistanceOverride (activationDistanceOverride), mFallback(fallbackMap), mPlayIntro(0), mTeleportEnabled(true), mLevitationEnabled(true), mFacedDistance(FLT_MAX), mGodMode(false), mContentFiles (contentFiles), - mGoToJail(false) + mGoToJail(false), + mStartCell (startCell) { mPhysics = new PhysicsSystem(renderer); mPhysEngine = mPhysics->getEngine(); @@ -168,7 +170,7 @@ namespace MWWorld mWorldScene = new Scene(*mRendering, mPhysics); } - void World::startNewGame() + void World::startNewGame (bool bypass) { mGoToJail = false; mLevitationEnabled = true; @@ -181,23 +183,44 @@ namespace MWWorld MWBase::Environment::get().getWindowManager()->updatePlayer(); - // FIXME: this will add cell 0,0 as visible on the global map - ESM::Position pos; - const int cellSize = 8192; - pos.pos[0] = cellSize/2; - pos.pos[1] = cellSize/2; - pos.pos[2] = 0; - pos.rot[0] = 0; - pos.rot[1] = 0; - pos.rot[2] = 0; - mWorldScene->changeToExteriorCell(pos); + if (bypass && !mStartCell.empty()) + { + ESM::Position pos; - // FIXME: should be set to 1, but the sound manager won't pause newly started sounds - mPlayIntro = 2; + if (findExteriorPosition (mStartCell, pos)) + { + changeToExteriorCell (pos); + } + else + { + findInteriorPosition (mStartCell, pos); + changeToInteriorCell (mStartCell, pos); + } + } + else + { + /// \todo if !bypass, do not add player location to global map for the duration of one + /// frame + ESM::Position pos; + const int cellSize = 8192; + pos.pos[0] = cellSize/2; + pos.pos[1] = cellSize/2; + pos.pos[2] = 0; + pos.rot[0] = 0; + pos.rot[1] = 0; + pos.rot[2] = 0; + mWorldScene->changeToExteriorCell(pos); + } - // set new game mark - mGlobalVariables["chargenstate"].setInteger (1); - mGlobalVariables["pcrace"].setInteger (3); + if (!bypass) + { + // FIXME: should be set to 1, but the sound manager won't pause newly started sounds + mPlayIntro = 2; + + // set new game mark + mGlobalVariables["chargenstate"].setInteger (1); + mGlobalVariables["pcrace"].setInteger (3); + } // we don't want old weather to persist on a new game delete mWeatherManager; @@ -1626,7 +1649,7 @@ namespace MWWorld if (ptr.getClass().getCreatureStats(ptr).isDead()) return false; - if (ptr.getClass().isFlying(ptr)) + if (ptr.getClass().canFly(ptr)) return true; const MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index b694e00f7..42f52cb61 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -120,6 +120,9 @@ namespace MWWorld std::map mMagicBolts; std::map mProjectiles; + + std::string mStartCell; + void updateWeather(float duration); int getDaysPerMonth (int month) const; @@ -179,11 +182,13 @@ namespace MWWorld const Files::Collections& fileCollections, const std::vector& contentFiles, const boost::filesystem::path& resDir, const boost::filesystem::path& cacheDir, - ToUTF8::Utf8Encoder* encoder, const std::map& fallbackMap, int mActivationDistanceOverride); + ToUTF8::Utf8Encoder* encoder, const std::map& fallbackMap, + int activationDistanceOverride, const std::string& startCell); virtual ~World(); - virtual void startNewGame(); + virtual void startNewGame (bool bypass); + ///< \param bypass Bypass regular game start. virtual void clear(); diff --git a/cmake/OpenMWMacros.cmake b/cmake/OpenMWMacros.cmake index f66dbf2c4..c5669fa70 100644 --- a/cmake/OpenMWMacros.cmake +++ b/cmake/OpenMWMacros.cmake @@ -1,26 +1,75 @@ +function(enable_unity_build UB_SUFFIX SOURCE_VARIABLE_NAME) + set(files ${SOURCE_VARIABLE_NAME}) + # Generate a unique filename for the unity build translation unit + set(unit_build_file ${CMAKE_CURRENT_BINARY_DIR}/ub_${UB_SUFFIX}.cpp) + # Exclude all translation units from compilation + set_source_files_properties(${files} PROPERTIES HEADER_FILE_ONLY true) + # Open the ub file + FILE(WRITE ${unit_build_file} "// Unity Build generated by CMake\n") + # Add include statement for each translation unit + foreach(source_file ${files} ) + FILE( APPEND ${unit_build_file} "#include <${source_file}>\n") + endforeach(source_file) + # Complement list of translation units with the name of ub + set(${SOURCE_VARIABLE_NAME} ${${SOURCE_VARIABLE_NAME}} ${unit_build_file} PARENT_SCOPE) +endfunction(enable_unity_build) + + macro (add_openmw_dir dir) -set (files) -foreach (u ${ARGN}) -file (GLOB ALL "${dir}/${u}.[ch]pp") -foreach (f ${ALL}) -list (APPEND files "${f}") -list (APPEND OPENMW_FILES "${f}") -endforeach (f) -endforeach (u) -source_group ("apps\\openmw\\${dir}" FILES ${files}) + set (files) + set (cppfiles) + foreach (u ${ARGN}) + + # Add cpp and hpp to OPENMW_FILES + file (GLOB ALL "${dir}/${u}.[ch]pp") + foreach (f ${ALL}) + list (APPEND files "${f}") + list (APPEND OPENMW_FILES "${f}") + endforeach (f) + + # Add cpp to unity build + file (GLOB ALL "${dir}/${u}.cpp") + foreach (f ${ALL}) + list (APPEND cppfiles "${f}") + endforeach (f) + + endforeach (u) + + if (OPENMW_UNITY_BUILD) + enable_unity_build(${dir} "${cppfiles}") + list (APPEND OPENMW_FILES ${CMAKE_CURRENT_BINARY_DIR}/ub_${dir}.cpp) + endif() + + source_group ("apps\\openmw\\${dir}" FILES ${files}) endmacro (add_openmw_dir) macro (add_component_dir dir) -set (files) -foreach (u ${ARGN}) -file (GLOB ALL "${dir}/${u}.[ch]pp") -foreach (f ${ALL}) -list (APPEND files "${f}") -list (APPEND COMPONENT_FILES "${f}") -endforeach (f) -endforeach (u) -source_group ("components\\${dir}" FILES ${files}) + set (files) + set (cppfiles) + + foreach (u ${ARGN}) + file (GLOB ALL "${dir}/${u}.[ch]pp") + + foreach (f ${ALL}) + list (APPEND files "${f}") + list (APPEND COMPONENT_FILES "${f}") + endforeach (f) + + # Add cpp to unity build + file (GLOB ALL "${dir}/${u}.cpp") + foreach (f ${ALL}) + list (APPEND cppfiles "${f}") + endforeach (f) + + endforeach (u) + + if (OPENMW_UNITY_BUILD) + enable_unity_build(${dir} "${cppfiles}") + list (APPEND COMPONENT_FILES ${CMAKE_CURRENT_BINARY_DIR}/ub_${dir}.cpp) + endif() + + source_group ("components\\${dir}" FILES ${files}) endmacro (add_component_dir) macro (add_component_qt_dir dir) diff --git a/components/bsa/bsa_file.cpp b/components/bsa/bsa_file.cpp index 8db4fa888..25b006fb3 100644 --- a/components/bsa/bsa_file.cpp +++ b/components/bsa/bsa_file.cpp @@ -111,7 +111,7 @@ void BSAFile::readHeader() fail("Directory information larger than entire archive"); // Read the offset info into a temporary buffer - vector offsets(3*filenum); + std::vector offsets(3*filenum); input.read(reinterpret_cast(&offsets[0]), 12*filenum); // Read the string table diff --git a/components/compiler/declarationparser.cpp b/components/compiler/declarationparser.cpp index d17f49caf..7961b8f41 100644 --- a/components/compiler/declarationparser.cpp +++ b/components/compiler/declarationparser.cpp @@ -18,7 +18,7 @@ bool Compiler::DeclarationParser::parseName (const std::string& name, const Toke { if (mState==State_Name) { - std::string name2 = Misc::StringUtils::lowerCase (name); + std::string name2 = ::Misc::StringUtils::lowerCase (name); char type = mLocals.getType (name2); @@ -80,4 +80,4 @@ bool Compiler::DeclarationParser::parseSpecial (int code, const TokenLoc& loc, S void Compiler::DeclarationParser::reset() { mState = State_Begin; -} \ No newline at end of file +} diff --git a/components/interpreter/runtime.cpp b/components/interpreter/runtime.cpp index bb0dffb87..c71aef95c 100644 --- a/components/interpreter/runtime.cpp +++ b/components/interpreter/runtime.cpp @@ -50,7 +50,7 @@ namespace Interpreter return literalBlock+offset; } - void Runtime::configure (const Interpreter::Type_Code *code, int codeSize, Context& context) + void Runtime::configure (const Type_Code *code, int codeSize, Context& context) { clear(); diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp index 40a8baaf0..a14fe1f84 100644 --- a/components/terrain/quadtreenode.cpp +++ b/components/terrain/quadtreenode.cpp @@ -282,9 +282,6 @@ bool QuadTreeNode::update(const Ogre::Vector3 &cameraPos) size_t wantedLod = 0; float cellWorldSize = mTerrain->getStorage()->getCellWorldSize(); - if (!mTerrain->getDistantLandEnabled() && dist > cellWorldSize) - return true; - if (dist > cellWorldSize*64) wantedLod = 6; else if (dist > cellWorldSize*32) @@ -357,6 +354,7 @@ bool QuadTreeNode::update(const Ogre::Vector3 &cameraPos) if (!childrenLoaded) { + mChunk->setVisible(true); // Make sure child scene nodes are detached until all children are loaded mSceneNode->removeAllChildren(); } @@ -391,6 +389,8 @@ void QuadTreeNode::load(const LoadResponseData &data) mChunk = new Chunk(mTerrain->getBufferCache().getUVBuffer(), mBounds, data); mChunk->setVisibilityFlags(mTerrain->getVisiblityFlags()); mChunk->setCastShadows(true); + if (!mTerrain->getDistantLandEnabled()) + mChunk->setRenderingDistance(8192); mSceneNode->attachObject(mChunk); mMaterialGenerator->enableShadows(mTerrain->getShadowsEnabled()); @@ -437,7 +437,7 @@ void QuadTreeNode::unload(bool recursive) if (recursive && hasChildren()) { for (int i=0; i<4; ++i) - mChildren[i]->unload(); + mChildren[i]->unload(true); } } diff --git a/credits.txt b/credits.txt index 601255763..28a30e907 100644 --- a/credits.txt +++ b/credits.txt @@ -82,12 +82,14 @@ Sandy Carter (bwrsandman) - Arch Linux Public Relations and Translations: +Alex McKibben (WeirdSexy) - Podcaster Artem Kotsynyak (greye) - Russian News Writer +Jim Clauwaert (Zedd) - Public Outreach Julien Voisin (jvoisin/ap0) - French News Writer +Lukasz Gromanowski (lgro) - English News Writer Mickey Lyle (raevol) - Release Manager Pithorn - Chinese News Writer -sir_herrbatka - English/Polish News Writer -Alex McKibben (WeirdSexy) - Podcaster +sir_herrbatka - Polish News Writer Website: diff --git a/files/mygui/openmw_mainmenu.layout b/files/mygui/openmw_mainmenu.layout index 4479a121f..e8cb23b77 100644 --- a/files/mygui/openmw_mainmenu.layout +++ b/files/mygui/openmw_mainmenu.layout @@ -2,5 +2,11 @@ - + + + + + + + diff --git a/files/opencs/add.png b/files/opencs/add.png new file mode 100644 index 000000000..3f1347e24 Binary files /dev/null and b/files/opencs/add.png differ diff --git a/files/opencs/edit-clone.png b/files/opencs/edit-clone.png new file mode 100644 index 000000000..3398e8d18 Binary files /dev/null and b/files/opencs/edit-clone.png differ diff --git a/files/opencs/edit-delete.png b/files/opencs/edit-delete.png new file mode 100644 index 000000000..ea03150a1 Binary files /dev/null and b/files/opencs/edit-delete.png differ diff --git a/files/opencs/edit-preview.png b/files/opencs/edit-preview.png new file mode 100644 index 000000000..a605e7080 Binary files /dev/null and b/files/opencs/edit-preview.png differ diff --git a/files/opencs/edit-undo.png b/files/opencs/edit-undo.png new file mode 100644 index 000000000..8b0fef9a8 Binary files /dev/null and b/files/opencs/edit-undo.png differ diff --git a/files/opencs/go-next.png b/files/opencs/go-next.png new file mode 100644 index 000000000..6ef8de76e Binary files /dev/null and b/files/opencs/go-next.png differ diff --git a/files/opencs/go-previous.png b/files/opencs/go-previous.png new file mode 100644 index 000000000..659cd90d7 Binary files /dev/null and b/files/opencs/go-previous.png differ diff --git a/files/opencs/resources.qrc b/files/opencs/resources.qrc index 2b1e65ff0..2031e54cc 100644 --- a/files/opencs/resources.qrc +++ b/files/opencs/resources.qrc @@ -57,6 +57,13 @@ static.png weapon.png multitype.png + go-next.png + go-previous.png + edit-delete.png + edit-undo.png + edit-preview.png + edit-clone.png + add.png raster/startup/big/create-addon.png raster/startup/big/new-game.png raster/startup/big/edit-content.png diff --git a/readme.txt b/readme.txt index bb17a68e7..a054626dd 100644 --- a/readme.txt +++ b/readme.txt @@ -8,7 +8,6 @@ License: GPL (see GPL3.txt for more information) Website: http://www.openmw.org Font Licenses: -EBGaramond-Regular.ttf: OFL (see OFL.txt for more information) DejaVuLGCSansMono.ttf: custom (see DejaVu Font License.txt for more information)