diff --git a/apps/opencs/model/world/collection.hpp b/apps/opencs/model/world/collection.hpp index df144ba7b..d342e88a4 100644 --- a/apps/opencs/model/world/collection.hpp +++ b/apps/opencs/model/world/collection.hpp @@ -98,6 +98,10 @@ namespace CSMWorld UniversalId::Type type = UniversalId::Type_None); ///< \param type Will be ignored, unless the collection supports multiple record types + virtual void cloneRecord(const std::string& origin, + const std::string& destination, + const UniversalId::Type type); + virtual int searchId (const std::string& id) const; ////< Search record with \a id. /// \return index of record (if found) or -1 (not found) @@ -193,6 +197,19 @@ namespace CSMWorld return true; } + template + void Collection::cloneRecord(const std::string& origin, + const std::string& destination, + const UniversalId::Type type) + { + Record copy; + copy.mModified = getRecord(origin).get(); + copy.mState = RecordBase::State_ModifiedOnly; + copy.get().mId = destination; + + insertRecord(copy, getAppendIndex(destination, type)); + } + template Collection::Collection() {} diff --git a/apps/opencs/model/world/collectionbase.hpp b/apps/opencs/model/world/collectionbase.hpp index ab7a9ebec..408a89d92 100644 --- a/apps/opencs/model/world/collectionbase.hpp +++ b/apps/opencs/model/world/collectionbase.hpp @@ -74,6 +74,10 @@ namespace CSMWorld UniversalId::Type type = UniversalId::Type_None) = 0; ///< If the record type does not match, an exception is thrown. + virtual void cloneRecord(const std::string& origin, + const std::string& destination, + const UniversalId::Type type) = 0; + virtual const RecordBase& getRecord (const std::string& id) const = 0; virtual const RecordBase& getRecord (int index) const = 0; diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp index 21feb14be..b60ffeb29 100644 --- a/apps/opencs/model/world/commands.cpp +++ b/apps/opencs/model/world/commands.cpp @@ -4,10 +4,10 @@ #include #include "idtable.hpp" -#include "idtable.hpp" +#include CSMWorld::ModifyCommand::ModifyCommand (QAbstractItemModel& model, const QModelIndex& index, - const QVariant& new_, QUndoCommand *parent) + const QVariant& new_, QUndoCommand* parent) : QUndoCommand (parent), mModel (model), mIndex (index), mNew (new_) { mOld = mModel.data (mIndex, Qt::EditRole); @@ -25,7 +25,7 @@ void CSMWorld::ModifyCommand::undo() mModel.setData (mIndex, mOld); } -CSMWorld::CreateCommand::CreateCommand (IdTable& model, const std::string& id, QUndoCommand *parent) +CSMWorld::CreateCommand::CreateCommand (IdTable& model, const std::string& id, QUndoCommand* parent) : QUndoCommand (parent), mModel (model), mId (id), mType (UniversalId::Type_None) { setText (("Create record " + id).c_str()); @@ -54,7 +54,7 @@ void CSMWorld::CreateCommand::undo() mModel.removeRow (mModel.getModelIndex (mId, 0).row()); } -CSMWorld::RevertCommand::RevertCommand (IdTable& model, const std::string& id, QUndoCommand *parent) +CSMWorld::RevertCommand::RevertCommand (IdTable& model, const std::string& id, QUndoCommand* parent) : QUndoCommand (parent), mModel (model), mId (id), mOld (0) { setText (("Revert record " + id).c_str()); @@ -89,7 +89,7 @@ void CSMWorld::RevertCommand::undo() mModel.setRecord (mId, *mOld); } -CSMWorld::DeleteCommand::DeleteCommand (IdTable& model, const std::string& id, QUndoCommand *parent) +CSMWorld::DeleteCommand::DeleteCommand (IdTable& model, const std::string& id, QUndoCommand* parent) : QUndoCommand (parent), mModel (model), mId (id), mOld (0) { setText (("Delete record " + id).c_str()); @@ -126,7 +126,7 @@ void CSMWorld::DeleteCommand::undo() CSMWorld::ReorderRowsCommand::ReorderRowsCommand (IdTable& model, int baseIndex, - const std::vector& newOrder) + const std::vector& newOrder) : mModel (model), mBaseIndex (baseIndex), mNewOrder (newOrder) {} @@ -140,8 +140,35 @@ void CSMWorld::ReorderRowsCommand::undo() int size = static_cast (mNewOrder.size()); std::vector reverse (size); - for (int i=0; i::const_iterator iter (mValues.begin()); iter != mValues.end(); ++iter) + mModel.setData (mModel.getModelIndex (mIdDestination, iter->first), iter->second); +} + +void CSMWorld::CloneCommand::undo() +{ + mModel.removeRow (mModel.getModelIndex (mIdDestination, 0).row()); } \ No newline at end of file diff --git a/apps/opencs/model/world/commands.hpp b/apps/opencs/model/world/commands.hpp index f0480a369..ec6350658 100644 --- a/apps/opencs/model/world/commands.hpp +++ b/apps/opencs/model/world/commands.hpp @@ -39,6 +39,26 @@ namespace CSMWorld virtual void undo(); }; + class CloneCommand : public QUndoCommand + { + IdTable& mModel; + std::string mIdOrigin; + std::string mIdDestination; + UniversalId::Type mType; + std::map mValues; + + public: + + CloneCommand (IdTable& model, const std::string& idOrigin, + const std::string& IdDestination, + const UniversalId::Type type, + QUndoCommand* parent = 0); + + virtual void redo(); + + virtual void undo(); + }; + class CreateCommand : public QUndoCommand { IdTable& mModel; diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 04e35cdaa..8d53c4e53 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -154,14 +154,17 @@ CSMWorld::Data::Data() : mRefs (mCells) mTopics.addColumn (new StringIdColumn); mTopics.addColumn (new RecordStateColumn); + mTopics.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Topic)); mTopics.addColumn (new DialogueTypeColumn); mJournals.addColumn (new StringIdColumn); mJournals.addColumn (new RecordStateColumn); + mJournals.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Journal)); mJournals.addColumn (new DialogueTypeColumn (true)); mTopicInfos.addColumn (new StringIdColumn (true)); mTopicInfos.addColumn (new RecordStateColumn); + mTopicInfos.addColumn (new FixedRecordTypeColumn (UniversalId::Type_TopicInfo)); mTopicInfos.addColumn (new TopicColumn (false)); mTopicInfos.addColumn (new ActorColumn); mTopicInfos.addColumn (new RaceColumn); @@ -178,6 +181,7 @@ CSMWorld::Data::Data() : mRefs (mCells) mJournalInfos.addColumn (new StringIdColumn (true)); mJournalInfos.addColumn (new RecordStateColumn); + mJournalInfos.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Journal)); mJournalInfos.addColumn (new TopicColumn (true)); mJournalInfos.addColumn (new QuestStatusTypeColumn); mJournalInfos.addColumn (new QuestIndexColumn); @@ -194,6 +198,7 @@ CSMWorld::Data::Data() : mRefs (mCells) mRefs.addColumn (new StringIdColumn (true)); mRefs.addColumn (new RecordStateColumn); + mRefs.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Reference)); mRefs.addColumn (new CellColumn); mRefs.addColumn (new IdColumn); mRefs.addColumn (new PosColumn (&CellRef::mPos, 0, false)); @@ -224,6 +229,7 @@ CSMWorld::Data::Data() : mRefs (mCells) mFilters.addColumn (new StringIdColumn); mFilters.addColumn (new RecordStateColumn); + mFilters.addColumn (new FixedRecordTypeColumn (UniversalId::Type_Filter)); mFilters.addColumn (new FilterColumn); mFilters.addColumn (new DescriptionColumn); mFilters.addColumn (new ScopeColumn); diff --git a/apps/opencs/model/world/idtable.cpp b/apps/opencs/model/world/idtable.cpp index 809d64339..bea307aa2 100644 --- a/apps/opencs/model/world/idtable.cpp +++ b/apps/opencs/model/world/idtable.cpp @@ -124,6 +124,17 @@ void CSMWorld::IdTable::addRecord (const std::string& id, UniversalId::Type type endInsertRows(); } +void CSMWorld::IdTable::cloneRecord(const std::string& origin, + const std::string& destination, + CSMWorld::UniversalId::Type type) +{ + int index = mIdCollection->getAppendIndex (destination); + beginInsertRows (QModelIndex(), index, index); + mIdCollection->cloneRecord(origin, destination, type); + endInsertRows(); +} + + QModelIndex CSMWorld::IdTable::getModelIndex (const std::string& id, int column) const { return index (mIdCollection->getIndex (id), column); diff --git a/apps/opencs/model/world/idtable.hpp b/apps/opencs/model/world/idtable.hpp index e4ae58fd0..74923867d 100644 --- a/apps/opencs/model/world/idtable.hpp +++ b/apps/opencs/model/world/idtable.hpp @@ -63,6 +63,10 @@ namespace CSMWorld void addRecord (const std::string& id, UniversalId::Type type = UniversalId::Type_None); ///< \param type Will be ignored, unless the collection supports multiple record types + void cloneRecord(const std::string& origin, + const std::string& destination, + UniversalId::Type type = UniversalId::Type_None); + QModelIndex getModelIndex (const std::string& id, int column) const; void setRecord (const std::string& id, const RecordBase& record); diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp index 696aeefaa..6b0081258 100644 --- a/apps/opencs/model/world/refcollection.cpp +++ b/apps/opencs/model/world/refcollection.cpp @@ -5,6 +5,8 @@ #include "ref.hpp" #include "cell.hpp" +#include "universalid.hpp" +#include "record.hpp" void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool base) { @@ -34,4 +36,15 @@ std::string CSMWorld::RefCollection::getNewId() std::ostringstream stream; stream << "ref#" << mNextId++; return stream.str(); +} + +void CSMWorld::RefCollection::cloneRecord(const std::string& origin, + const std::string& destination, + const CSMWorld::UniversalId::Type type, + const CSMWorld::UniversalId::ArgumentType argumentType) +{ + Record clone(getRecord(origin)); + clone.mState = CSMWorld::RecordBase::State_ModifiedOnly; + clone.get().mId = destination; + insertRecord(clone, getAppendIndex(destination, type), type); } \ No newline at end of file diff --git a/apps/opencs/model/world/refcollection.hpp b/apps/opencs/model/world/refcollection.hpp index b5f8c8064..dcfd2036c 100644 --- a/apps/opencs/model/world/refcollection.hpp +++ b/apps/opencs/model/world/refcollection.hpp @@ -8,6 +8,7 @@ namespace CSMWorld { struct Cell; + struct UniversalId; /// \brief References in cells class RefCollection : public Collection @@ -25,6 +26,11 @@ namespace CSMWorld ///< Load a sequence of references. std::string getNewId(); + + void cloneRecord(const std::string& origin, + const std::string& destination, + const CSMWorld::UniversalId::Type type, + const CSMWorld::UniversalId::ArgumentType argumentType); }; } diff --git a/apps/opencs/model/world/refidadapter.hpp b/apps/opencs/model/world/refidadapter.hpp index df0ceae70..0870a2d3e 100644 --- a/apps/opencs/model/world/refidadapter.hpp +++ b/apps/opencs/model/world/refidadapter.hpp @@ -31,6 +31,7 @@ namespace CSMWorld ///< If the data type does not match an exception is thrown. virtual std::string getId (const RecordBase& record) const = 0; + virtual void setId(RecordBase& record, const std::string& id) = 0; }; } diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index d5d84a8f7..bd509a86b 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -34,6 +34,8 @@ namespace CSMWorld BaseRefIdAdapter (UniversalId::Type type, const BaseColumns& base); virtual std::string getId (const RecordBase& record) const; + + virtual void setId (RecordBase& record, const std::string& id); virtual QVariant getData (const RefIdColumn *column, const RefIdData& data, int index) const; @@ -50,6 +52,12 @@ namespace CSMWorld : mType (type), mBase (base) {} + template + void BaseRefIdAdapter::setId (RecordBase& record, const std::string& id) + { + (dynamic_cast&> (record).get().mId) = id; + } + template std::string BaseRefIdAdapter::getId (const RecordBase& record) const { diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index 176a19f2f..8a1af3509 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -2,6 +2,7 @@ #include "refidcollection.hpp" #include +#include #include @@ -449,6 +450,16 @@ void CSMWorld::RefIdCollection::replace (int index, const RecordBase& record) mData.getRecord (mData.globalToLocalIndex (index)).assign (record); } +void CSMWorld::RefIdCollection::cloneRecord(const std::string& origin, + const std::string& destination, + const CSMWorld::UniversalId::Type type) +{ + std::auto_ptr newRecord(mData.getRecord(mData.searchId(origin)).clone()); + newRecord->mState = RecordBase::State_ModifiedOnly; + mAdapters.find(type)->second->setId(*newRecord, destination); + mData.insertRecord(*newRecord, type, destination); +} + void CSMWorld::RefIdCollection::appendRecord (const RecordBase& record, UniversalId::Type type) { diff --git a/apps/opencs/model/world/refidcollection.hpp b/apps/opencs/model/world/refidcollection.hpp index 328680d85..dd6213677 100644 --- a/apps/opencs/model/world/refidcollection.hpp +++ b/apps/opencs/model/world/refidcollection.hpp @@ -69,6 +69,10 @@ namespace CSMWorld virtual void removeRows (int index, int count); + virtual void cloneRecord(const std::string& origin, + const std::string& destination, + const UniversalId::Type type); + virtual void appendBlankRecord (const std::string& id, UniversalId::Type type); ///< \param type Will be ignored, unless the collection supports multiple record types diff --git a/apps/opencs/model/world/refiddata.cpp b/apps/opencs/model/world/refiddata.cpp index 65990c1d4..9d980a99c 100644 --- a/apps/opencs/model/world/refiddata.cpp +++ b/apps/opencs/model/world/refiddata.cpp @@ -330,4 +330,18 @@ const CSMWorld::RefIdDataContainer< ESM::Repair >& CSMWorld::RefIdData::getRepai const CSMWorld::RefIdDataContainer< ESM::Static >& CSMWorld::RefIdData::getStatics() const { return mStatics; -} \ No newline at end of file +} + +void CSMWorld::RefIdData::insertRecord(CSMWorld::RecordBase& record, CSMWorld::UniversalId::Type type, const std::string& id) +{ + std::map::iterator iter = + mRecordContainers.find (type); + + if (iter==mRecordContainers.end()) + throw std::logic_error ("invalid local index type"); + + iter->second->insertRecord(record); + + mIndex.insert (std::make_pair (Misc::StringUtils::lowerCase (id), + LocalIndex (iter->second->getSize()-1, type))); +} diff --git a/apps/opencs/model/world/refiddata.hpp b/apps/opencs/model/world/refiddata.hpp index a204334b3..9fb143e81 100644 --- a/apps/opencs/model/world/refiddata.hpp +++ b/apps/opencs/model/world/refiddata.hpp @@ -46,6 +46,8 @@ namespace CSMWorld virtual RecordBase& getRecord (int index)= 0; virtual void appendRecord (const std::string& id) = 0; + + virtual void insertRecord (RecordBase& record) = 0; virtual void load (int index, ESM::ESMReader& reader, bool base) = 0; @@ -68,6 +70,8 @@ namespace CSMWorld virtual RecordBase& getRecord (int index); virtual void appendRecord (const std::string& id); + + virtual void insertRecord (RecordBase& record); virtual void load (int index, ESM::ESMReader& reader, bool base); @@ -78,6 +82,13 @@ namespace CSMWorld virtual void save (int index, ESM::ESMWriter& writer) const; }; + template + void RefIdDataContainer::insertRecord(RecordBase& record) + { + Record& newRecord = dynamic_cast& >(record); + mContainer.push_back(newRecord); + } + template int RefIdDataContainer::getSize() const { @@ -200,6 +211,8 @@ namespace CSMWorld LocalIndex searchId (const std::string& id) const; void erase (int index, int count); + + void insertRecord(CSMWorld::RecordBase& record, CSMWorld::UniversalId::Type type, const std::string& id); const RecordBase& getRecord (const LocalIndex& index) const; diff --git a/apps/opencs/view/world/cellcreator.cpp b/apps/opencs/view/world/cellcreator.cpp index 74fb06870..cdeee5655 100644 --- a/apps/opencs/view/world/cellcreator.cpp +++ b/apps/opencs/view/world/cellcreator.cpp @@ -61,6 +61,7 @@ void CSVWorld::CellCreator::reset() mX->setValue (0); mY->setValue (0); mType->setCurrentIndex (0); + setType(0); GenericCreator::reset(); } @@ -78,4 +79,25 @@ void CSVWorld::CellCreator::setType (int index) void CSVWorld::CellCreator::valueChanged (int index) { update(); -} \ No newline at end of file +} + +void CSVWorld::CellCreator::cloneMode(const std::string& originId, + const CSMWorld::UniversalId::Type type) +{ + CSVWorld::GenericCreator::cloneMode(originId, type); + if (*(originId.begin()) == '#') //if originid points to the exterior cell + { + setType(1); //enable x and y controls + mType->setCurrentIndex(1); + } else { + setType(0); + mType->setCurrentIndex(0); + } +} + + +void CSVWorld::CellCreator::toggleWidgets(bool active) +{ + CSVWorld::GenericCreator::toggleWidgets(active); + mType->setEnabled(active); +} diff --git a/apps/opencs/view/world/cellcreator.hpp b/apps/opencs/view/world/cellcreator.hpp index a5473e2c9..db9fbf8a3 100644 --- a/apps/opencs/view/world/cellcreator.hpp +++ b/apps/opencs/view/world/cellcreator.hpp @@ -29,6 +29,11 @@ namespace CSVWorld virtual void reset(); + virtual void toggleWidgets(bool active = true); + + virtual void cloneMode(const std::string& originId, + const CSMWorld::UniversalId::Type type); + private slots: void setType (int index); diff --git a/apps/opencs/view/world/creator.hpp b/apps/opencs/view/world/creator.hpp index df9b116ee..88da70330 100644 --- a/apps/opencs/view/world/creator.hpp +++ b/apps/opencs/view/world/creator.hpp @@ -2,6 +2,7 @@ #define CSV_WORLD_CREATOR_H #include +#include "../../model/world/universalid.hpp" class QUndoStack; @@ -24,8 +25,13 @@ namespace CSVWorld virtual void reset() = 0; + virtual void cloneMode(const std::string& originId, + const CSMWorld::UniversalId::Type type) = 0; + virtual void setEditLock (bool locked) = 0; + virtual void toggleWidgets(bool active = true) = 0; + signals: void done(); diff --git a/apps/opencs/view/world/genericcreator.cpp b/apps/opencs/view/world/genericcreator.cpp index ba2b3665a..cd7a5fa18 100644 --- a/apps/opencs/view/world/genericcreator.cpp +++ b/apps/opencs/view/world/genericcreator.cpp @@ -57,8 +57,15 @@ const CSMWorld::UniversalId& CSVWorld::GenericCreator::getCollectionId() const } CSVWorld::GenericCreator::GenericCreator (CSMWorld::Data& data, QUndoStack& undoStack, - const CSMWorld::UniversalId& id, bool relaxedIdRules) -: mData (data), mUndoStack (undoStack), mListId (id), mLocked (false) + const CSMWorld::UniversalId& id, bool relaxedIdRules): + + mData (data), + mUndoStack (undoStack), + mListId (id), + mLocked (false), + mCloneMode(false), + mClonedType(CSMWorld::UniversalId::Type_None) + { mLayout = new QHBoxLayout; mLayout->setContentsMargins (0, 0, 0, 0); @@ -89,6 +96,7 @@ void CSVWorld::GenericCreator::setEditLock (bool locked) void CSVWorld::GenericCreator::reset() { + mCloneMode = false; mId->setText (""); update(); } @@ -120,16 +128,40 @@ void CSVWorld::GenericCreator::create() { if (!mLocked) { - std::string id = getId(); + if (mCloneMode) + { + std::string id = getId(); + std::auto_ptr command (new CSMWorld::CloneCommand ( + dynamic_cast (*mData.getTableModel(mListId)), mClonedId, id, mClonedType)); + + mUndoStack.push(command.release()); + + emit done(); + emit requestFocus(id); + } else { + std::string id = getId(); - std::auto_ptr command (new CSMWorld::CreateCommand ( + std::auto_ptr command (new CSMWorld::CreateCommand ( dynamic_cast (*mData.getTableModel (mListId)), id)); - configureCreateCommand (*command); + configureCreateCommand (*command); - mUndoStack.push (command.release()); + mUndoStack.push (command.release()); - emit done(); - emit requestFocus (id); + emit done(); + emit requestFocus (id); + } } -} \ No newline at end of file +} + +void CSVWorld::GenericCreator::cloneMode(const std::string& originId, + const CSMWorld::UniversalId::Type type) +{ + mCloneMode = true; + mClonedId = originId; + mClonedType = type; +} + +void CSVWorld::GenericCreator::toggleWidgets(bool active) +{ +} diff --git a/apps/opencs/view/world/genericcreator.hpp b/apps/opencs/view/world/genericcreator.hpp index 8dd2ca911..714853f98 100644 --- a/apps/opencs/view/world/genericcreator.hpp +++ b/apps/opencs/view/world/genericcreator.hpp @@ -1,6 +1,7 @@ #ifndef CSV_WORLD_GENERICCREATOR_H #define CSV_WORLD_GENERICCREATOR_H +class QString; class QPushButton; class QLineEdit; class QHBoxLayout; @@ -28,6 +29,11 @@ namespace CSVWorld std::string mErrors; QHBoxLayout *mLayout; bool mLocked; + std::string mClonedId; + CSMWorld::UniversalId::Type mClonedType; + + protected: + bool mCloneMode; protected: @@ -57,11 +63,15 @@ namespace CSVWorld virtual void reset(); + virtual void toggleWidgets (bool active = true); + + virtual void cloneMode(const std::string& originId, + const CSMWorld::UniversalId::Type type); + virtual std::string getErrors() const; ///< Return formatted error descriptions for the current state of the creator. if an empty /// string is returned, there is no error. - private slots: void textChanged (const QString& text); diff --git a/apps/opencs/view/world/referenceablecreator.cpp b/apps/opencs/view/world/referenceablecreator.cpp index 718fe9ca7..7a5fca853 100644 --- a/apps/opencs/view/world/referenceablecreator.cpp +++ b/apps/opencs/view/world/referenceablecreator.cpp @@ -40,4 +40,10 @@ void CSVWorld::ReferenceableCreator::reset() { mType->setCurrentIndex (0); GenericCreator::reset(); -} \ No newline at end of file +} + +void CSVWorld::ReferenceableCreator::toggleWidgets(bool active) +{ + CSVWorld::GenericCreator::toggleWidgets(active); + mType->setEnabled(active); +} diff --git a/apps/opencs/view/world/referenceablecreator.hpp b/apps/opencs/view/world/referenceablecreator.hpp index 06e0e582b..88545575e 100644 --- a/apps/opencs/view/world/referenceablecreator.hpp +++ b/apps/opencs/view/world/referenceablecreator.hpp @@ -23,6 +23,7 @@ namespace CSVWorld const CSMWorld::UniversalId& id); virtual void reset(); + virtual void toggleWidgets(bool active = true); }; } diff --git a/apps/opencs/view/world/referencecreator.cpp b/apps/opencs/view/world/referencecreator.cpp index aef25a81d..6b8e02da0 100644 --- a/apps/opencs/view/world/referencecreator.cpp +++ b/apps/opencs/view/world/referencecreator.cpp @@ -40,15 +40,20 @@ CSVWorld::ReferenceCreator::ReferenceCreator (CSMWorld::Data& data, QUndoStack& void CSVWorld::ReferenceCreator::reset() { + GenericCreator::reset(); mCell->setText (""); mId = getData().getReferences().getNewId(); - GenericCreator::reset(); } std::string CSVWorld::ReferenceCreator::getErrors() const { std::string errors = GenericCreator::getErrors(); + if (mCloneMode) + { + return errors; + } + std::string cell = mCell->text().toUtf8().constData(); if (cell.empty()) @@ -72,4 +77,17 @@ std::string CSVWorld::ReferenceCreator::getErrors() const void CSVWorld::ReferenceCreator::cellChanged() { update(); -} \ No newline at end of file +} + +void CSVWorld::ReferenceCreator::toggleWidgets(bool active) +{ + CSVWorld::GenericCreator::toggleWidgets(active); + mCell->setEnabled(active); +} + +void CSVWorld::ReferenceCreator::cloneMode(const std::string& originId, + const CSMWorld::UniversalId::Type type) +{ + CSVWorld::GenericCreator::cloneMode(originId, type); + cellChanged(); //otherwise ok button will remain disabled +} diff --git a/apps/opencs/view/world/referencecreator.hpp b/apps/opencs/view/world/referencecreator.hpp index 27f81564f..12fb12dd9 100644 --- a/apps/opencs/view/world/referencecreator.hpp +++ b/apps/opencs/view/world/referencecreator.hpp @@ -25,7 +25,11 @@ namespace CSVWorld ReferenceCreator (CSMWorld::Data& data, QUndoStack& undoStack, const CSMWorld::UniversalId& id); + virtual void cloneMode(const std::string& originId, + const CSMWorld::UniversalId::Type type); + virtual void reset(); + virtual void toggleWidgets(bool active = true); virtual std::string getErrors() const; ///< Return formatted error descriptions for the current state of the creator. if an empty diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 71bdb9000..8f6fc46a8 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -28,7 +28,11 @@ void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) if (!mEditLock) { if (selectedRows.size()==1) + { menu.addAction (mEditAction); + if (mCreateAction) + menu.addAction(mCloneAction); + } if (mCreateAction) menu.addAction (mCreateAction); @@ -155,7 +159,7 @@ std::vector CSVWorld::Table::listDeletableSelectedIds() const CSVWorld::Table::Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, QUndoStack& undoStack, bool createAndDelete, bool sorting) - : mUndoStack (undoStack), mCreateAction (0), mEditLock (false), mRecordStatusDisplay (0) + : mUndoStack (undoStack), mCreateAction (0), mCloneAction(0), mEditLock (false), mRecordStatusDisplay (0) { mModel = &dynamic_cast (*data.getTableModel (id)); @@ -199,6 +203,10 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, CSMWorld::Data& data, Q mCreateAction = new QAction (tr ("Add Record"), this); connect (mCreateAction, SIGNAL (triggered()), this, SIGNAL (createRequest())); addAction (mCreateAction); + + mCloneAction = new QAction (tr ("Clone Record"), this); + connect(mCloneAction, SIGNAL (triggered()), this, SLOT (cloneRecord())); + addAction(mCloneAction); } mRevertAction = new QAction (tr ("Revert Record"), this); @@ -295,6 +303,19 @@ void CSVWorld::Table::editRecord() } } +void CSVWorld::Table::cloneRecord() +{ + if (!mEditLock) + { + QModelIndexList selectedRows = selectionModel()->selectedRows(); + const CSMWorld::UniversalId& toClone = getUniversalId(selectedRows.begin()->row()); + if (selectedRows.size()==1 && !mModel->getRecord(toClone.getId()).isDeleted()) + { + emit cloneRequest (toClone); + } + } +} + void CSVWorld::Table::moveUpRecord() { QModelIndexList selectedRows = selectionModel()->selectedRows(); diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp index 889e2847a..d30083333 100644 --- a/apps/opencs/view/world/table.hpp +++ b/apps/opencs/view/world/table.hpp @@ -32,6 +32,7 @@ namespace CSVWorld QUndoStack& mUndoStack; QAction *mEditAction; QAction *mCreateAction; + QAction *mCloneAction; QAction *mRevertAction; QAction *mDeleteAction; QAction *mMoveUpAction; @@ -73,6 +74,7 @@ namespace CSVWorld /// \param modified Number of added and modified records void createRequest(); + void cloneRequest(const CSMWorld::UniversalId&); private slots: @@ -82,6 +84,8 @@ namespace CSVWorld void editRecord(); + void cloneRecord(); + void moveUpRecord(); void moveDownRecord(); diff --git a/apps/opencs/view/world/tablebottombox.cpp b/apps/opencs/view/world/tablebottombox.cpp index 3edf9af31..239c7410f 100644 --- a/apps/opencs/view/world/tablebottombox.cpp +++ b/apps/opencs/view/world/tablebottombox.cpp @@ -153,7 +153,19 @@ void CSVWorld::TableBottomBox::tableSizeChanged (int size, int deleted, int modi void CSVWorld::TableBottomBox::createRequest() { mCreator->reset(); + mCreator->toggleWidgets(true); mLayout->setCurrentWidget (mCreator); setVisible (true); mCreating = true; -} \ No newline at end of file +} + +void CSVWorld::TableBottomBox::cloneRequest(const std::string& id, + const CSMWorld::UniversalId::Type type) +{ + mCreator->reset(); + mCreator->cloneMode(id, type); + mLayout->setCurrentWidget(mCreator); + mCreator->toggleWidgets(false); + setVisible (true); + mCreating = true; +} diff --git a/apps/opencs/view/world/tablebottombox.hpp b/apps/opencs/view/world/tablebottombox.hpp index a5ae5e0bd..8f0d85163 100644 --- a/apps/opencs/view/world/tablebottombox.hpp +++ b/apps/opencs/view/world/tablebottombox.hpp @@ -2,6 +2,7 @@ #define CSV_WORLD_BOTTOMBOX_H #include +#include class QLabel; class QStackedLayout; @@ -76,6 +77,8 @@ namespace CSVWorld /// \param modified Number of added and modified records void createRequest(); + void cloneRequest(const std::string& id, + const CSMWorld::UniversalId::Type type); }; } diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp index 55ded09de..981faaf59 100644 --- a/apps/opencs/view/world/tablesubview.cpp +++ b/apps/opencs/view/world/tablesubview.cpp @@ -6,7 +6,6 @@ #include "../../model/doc/document.hpp" #include "../filter/filterbox.hpp" - #include "table.hpp" #include "tablebottombox.hpp" #include "creator.hpp" @@ -46,8 +45,15 @@ CSVWorld::TableSubView::TableSubView (const CSMWorld::UniversalId& id, CSMDoc::D mTable->selectionSizeUpdate(); if (mBottom->canCreateAndDelete()) + { connect (mTable, SIGNAL (createRequest()), mBottom, SLOT (createRequest())); + connect (mTable, SIGNAL (cloneRequest(const CSMWorld::UniversalId&)), this, + SLOT(cloneRequest(const CSMWorld::UniversalId&))); + + connect (this, SIGNAL(cloneRequest(const std::string&, const CSMWorld::UniversalId::Type)), + mBottom, SLOT(cloneRequest(const std::string&, const CSMWorld::UniversalId::Type))); + } connect (mBottom, SIGNAL (requestFocus (const std::string&)), mTable, SLOT (requestFocus (const std::string&))); @@ -75,4 +81,9 @@ void CSVWorld::TableSubView::updateEditorSetting(const QString &settingName, con void CSVWorld::TableSubView::setStatusBar (bool show) { mBottom->setStatusBar (show); -} \ No newline at end of file +} + +void CSVWorld::TableSubView::cloneRequest(const CSMWorld::UniversalId& toClone) +{ + emit cloneRequest(toClone.getId(), toClone.getType()); +} diff --git a/apps/opencs/view/world/tablesubview.hpp b/apps/opencs/view/world/tablesubview.hpp index 56a441a4d..d728dc2f3 100644 --- a/apps/opencs/view/world/tablesubview.hpp +++ b/apps/opencs/view/world/tablesubview.hpp @@ -5,6 +5,11 @@ class QModelIndex; +namespace CSMWorld +{ + class IdTable; +} + namespace CSMDoc { class Document; @@ -34,9 +39,14 @@ namespace CSVWorld virtual void setStatusBar (bool show); + signals: + void cloneRequest(const std::string&, + const CSMWorld::UniversalId::Type); + private slots: void editRequest (int row); + void cloneRequest (const CSMWorld::UniversalId& toClone); }; } diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 914fbded2..9c298b9c1 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -3,6 +3,7 @@ #include #include +#include namespace Ogre { @@ -159,6 +160,9 @@ namespace MWBase virtual bool isAIActive() = 0; virtual void getObjectsInRange (const Ogre::Vector3& position, float radius, std::vector& objects) = 0; + + ///return the list of actors which are following the given actor (ie AiFollow is active and the target is the actor) + virtual std::list getActorsFollowing(const MWWorld::Ptr& actor) = 0; }; } diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 35783bd9f..b80fa9d7c 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -28,6 +28,7 @@ #include "../mwbase/mechanicsmanager.hpp" #include "aicombat.hpp" +#include "aifollow.hpp" namespace { @@ -795,7 +796,7 @@ namespace MWMechanics } // If it's the player and God Mode is turned on, keep it alive - if(iter->first.getRefData().getHandle()=="player" && + if(iter->first.getRefData().getHandle()=="player" && MWBase::Environment::get().getWorld()->getGodModeState()) { MWMechanics::DynamicStat stat(stats.getHealth()); @@ -928,4 +929,22 @@ namespace MWMechanics out.push_back(iter->first); } } + + std::list Actors::getActorsFollowing(const MWWorld::Ptr& actor) + { + std::list list; + for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();iter++) + { + const MWWorld::Class &cls = MWWorld::Class::get(iter->first); + CreatureStats &stats = cls.getCreatureStats(iter->first); + + if(stats.getAiSequence().getTypeId() == AiPackage::TypeIdFollow) + { + MWMechanics::AiFollow* package = static_cast(stats.getAiSequence().getActivePackage()); + if(package->getFollowedActor() == actor.getCellRef().mRefID) + list.push_front(iter->first); + } + } + return list; + } } diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 78aae6861..4b18ac862 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -84,7 +84,7 @@ namespace MWMechanics int getHoursToRest(const MWWorld::Ptr& ptr) const; ///< Calculate how many hours the given actor needs to rest in order to be fully healed - + int countDeaths (const std::string& id) const; ///< Return the number of deaths for actors with the given ID. @@ -94,7 +94,10 @@ namespace MWMechanics void skipAnimation(const MWWorld::Ptr& ptr); bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName); - void getObjectsInRange(const Ogre::Vector3& position, float radius, std::vector& out); + void getObjectsInRange(const Ogre::Vector3& position, float radius, std::vector& out); + + std::list getActorsFollowing(const MWWorld::Ptr& actor); + ///(rand())/RAND_MAX; mCombatMove = true; @@ -258,7 +258,8 @@ namespace MWMechanics // TODO: use movement settings instead of rotating directly MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false); - //MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; + //mMovement.mRotation[2] = 10*(Ogre::Degree(zAngle).valueRadians()-pos.rot[2]); + mMovement.mPosition[1] = 1; mReadyToAttack = false; } @@ -280,7 +281,7 @@ namespace MWMechanics float s1 = distBetween - weapRange; float t = s1/speed1; float s2 = speed2 * t; - float t_swing = 0.17f/weapSpeed;//0.17 should be the time of playing weapon anim from 'start' to 'hit' tags + float t_swing = 0.17f/weapSpeed;//instead of 0.17 should be the time of playing weapon anim from 'start' to 'hit' tags if (t + s2/speed1 <= t_swing) { mReadyToAttack = true; @@ -326,24 +327,12 @@ namespace MWMechanics start.mY = pos.pos[1]; start.mZ = pos.pos[2]; - const ESM::Pathgrid *pathgrid = - MWBase::Environment::get().getWorld()->getStore().get().search(*actor.getCell()->mCell); - - float xCell = 0; - float yCell = 0; - - if (actor.getCell()->mCell->isExterior()) - { - xCell = actor.getCell()->mCell->mData.mX * ESM::Land::REAL_SIZE; - yCell = actor.getCell()->mCell->mData.mY * ESM::Land::REAL_SIZE; - } - if(!mPathFinder.isPathConstructed()) - mPathFinder.buildPath(start, dest, pathgrid, xCell, yCell, isOutside); + mPathFinder.buildPath(start, dest, actor.getCell(), isOutside); else { PathFinder newPathFinder; - newPathFinder.buildPath(start, dest, pathgrid, xCell, yCell, isOutside); + newPathFinder.buildPath(start, dest, actor.getCell(), isOutside); //TO EXPLORE: //maybe here is a mistake (?): PathFinder::getPathSize() returns number of grid points in the path, @@ -421,4 +410,4 @@ void chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement movement.mPosition[1] = movement.mPosition[0] = 0; } -} \ No newline at end of file +} diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index 164671f4e..901b8c31d 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -28,8 +28,8 @@ namespace MWMechanics { AiEscort::AiEscort(const std::string &actorId, int duration, float x, float y, float z) : mActorId(actorId), mX(x), mY(y), mZ(z), mDuration(duration) - , cellX(std::numeric_limits::max()) - , cellY(std::numeric_limits::max()) + , mCellX(std::numeric_limits::max()) + , mCellY(std::numeric_limits::max()) { mMaxDist = 470; @@ -47,8 +47,8 @@ namespace MWMechanics AiEscort::AiEscort(const std::string &actorId, const std::string &cellId,int duration, float x, float y, float z) : mActorId(actorId), mCellId(cellId), mX(x), mY(y), mZ(z), mDuration(duration) - , cellX(std::numeric_limits::max()) - , cellY(std::numeric_limits::max()) + , mCellX(std::numeric_limits::max()) + , mCellY(std::numeric_limits::max()) { mMaxDist = 470; @@ -84,9 +84,7 @@ namespace MWMechanics MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); ESM::Position pos = actor.getRefData().getPosition(); - bool cellChange = actor.getCell()->mCell->mData.mX != cellX || actor.getCell()->mCell->mData.mY != cellY; - const ESM::Pathgrid *pathgrid = - MWBase::Environment::get().getWorld()->getStore().get().search(*actor.getCell()->mCell); + bool cellChange = actor.getCell()->mCell->mData.mX != mCellX || actor.getCell()->mCell->mData.mY != mCellY; if(actor.getCell()->mCell->mData.mX != player.getCell()->mCell->mData.mX) { @@ -116,15 +114,8 @@ namespace MWMechanics if(!mPathFinder.isPathConstructed() || cellChange) { - cellX = actor.getCell()->mCell->mData.mX; - cellY = actor.getCell()->mCell->mData.mY; - float xCell = 0; - float yCell = 0; - if (actor.getCell()->mCell->isExterior()) - { - xCell = actor.getCell()->mCell->mData.mX * ESM::Land::REAL_SIZE; - yCell = actor.getCell()->mCell->mData.mY * ESM::Land::REAL_SIZE; - } + mCellX = actor.getCell()->mCell->mData.mX; + mCellY = actor.getCell()->mCell->mData.mY; ESM::Pathgrid::Point dest; dest.mX = mX; @@ -136,7 +127,7 @@ namespace MWMechanics start.mY = pos.pos[1]; start.mZ = pos.pos[2]; - mPathFinder.buildPath(start, dest, pathgrid, xCell, yCell, true); + mPathFinder.buildPath(start, dest, actor.getCell(), true); } if(mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2])) diff --git a/apps/openmw/mwmechanics/aiescort.hpp b/apps/openmw/mwmechanics/aiescort.hpp index f3f6d2bd9..53b57c058 100644 --- a/apps/openmw/mwmechanics/aiescort.hpp +++ b/apps/openmw/mwmechanics/aiescort.hpp @@ -34,8 +34,8 @@ namespace MWMechanics unsigned int mDuration; PathFinder mPathFinder; - int cellX; - int cellY; + int mCellX; + int mCellY; }; } #endif diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index c5b1f1bf3..10bff8356 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -1,27 +1,120 @@ -#include "aifollow.hpp" -#include +#include "aifollow.hpp" +#include +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" +#include "../mwworld/class.hpp" +#include "movement.hpp" + +#include -MWMechanics::AiFollow::AiFollow(const std::string &actorId,float duration, float x, float y, float z) -: mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId) +MWMechanics::AiFollow::AiFollow(const std::string &actorId,float duration, float x, float y, float z) +: mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(""), mTimer(0), mStuckTimer(0) { } MWMechanics::AiFollow::AiFollow(const std::string &actorId,const std::string &cellId,float duration, float x, float y, float z) -: mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(cellId) +: mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(cellId), mTimer(0), mStuckTimer(0) { } + +bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration) +{ + const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr(mActorId, false); + + mTimer = mTimer + duration; + mStuckTimer = mStuckTimer + duration; + mTotalTime = mTotalTime + duration; + + ESM::Position pos = actor.getRefData().getPosition(); + + if(mTotalTime > mDuration && mDuration != 0) + return true; + + if((pos.pos[0]-mX)*(pos.pos[0]-mX) + + (pos.pos[1]-mY)*(pos.pos[1]-mY) + + (pos.pos[2]-mZ)*(pos.pos[2]-mZ) < 100*100) + { + if(actor.getCell()->isExterior()) + { + if(mCellId == "") + return true; + } + else + { + if(mCellId == actor.getCell()->mCell->mName) + return true; + } + } + + ESM::Pathgrid::Point dest; + dest.mX = target.getRefData().getPosition().pos[0]; + dest.mY = target.getRefData().getPosition().pos[1]; + dest.mZ = target.getRefData().getPosition().pos[2]; + + ESM::Pathgrid::Point start; + start.mX = pos.pos[0]; + start.mY = pos.pos[1]; + start.mZ = pos.pos[2]; + + if(mPathFinder.getPath().empty()) + mPathFinder.buildPath(start, dest, actor.getCell(), true); + + + if(mTimer > 0.25) + { + if(!mPathFinder.getPath().empty()) + { + ESM::Pathgrid::Point lastPos = mPathFinder.getPath().back(); + + if((dest.mX - lastPos.mX)*(dest.mX - lastPos.mX) + +(dest.mY - lastPos.mY)*(dest.mY - lastPos.mY) + +(dest.mZ - lastPos.mZ)*(dest.mZ - lastPos.mZ) + > 100*100) + mPathFinder.addPointToPath(dest); + } + + mTimer = 0; + } + + if(mStuckTimer>0.5) + { + if((mStuckPos.pos[0] - pos.pos[0])*(mStuckPos.pos[0] - pos.pos[0]) + +(mStuckPos.pos[1] - pos.pos[1])*(mStuckPos.pos[1] - pos.pos[1]) + +(mStuckPos.pos[2] - pos.pos[2])*(mStuckPos.pos[2] - pos.pos[2]) < 100) //NPC is stuck + mPathFinder.buildPath(start, dest, actor.getCell(), true); + + mStuckTimer = 0; + mStuckPos = pos; + } + + if(!mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2])) + { + float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); + //MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false); + MWWorld::Class::get(actor).getMovementSettings(actor).mRotation[2] = 10*(Ogre::Degree(zAngle).valueRadians()-pos.rot[2]); + //std::cout << Ogre::Degree(zAngle).valueDegrees()-Ogre::Radian(actor.getRefData().getPosition().rot[2]).valueDegrees() << " "<< pos.rot[2] << " " << zAngle << "\n"; + //MWWorld::Class::get(actor).get + } + if((dest.mX - pos.pos[0])*(dest.mX - pos.pos[0])+(dest.mY - pos.pos[1])*(dest.mY - pos.pos[1])+(dest.mZ - pos.pos[2])*(dest.mZ - pos.pos[2]) + < 100*100) + MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0; + else + MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; + + return false; +} + +std::string MWMechanics::AiFollow::getFollowedActor() +{ + return mActorId; +} + MWMechanics::AiFollow *MWMechanics::AiFollow::clone() const { return new AiFollow(*this); } - bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration) -{ - std::cout << "AiFollow completed.\n"; - return true; -} - int MWMechanics::AiFollow::getTypeId() const { return TypeIdFollow; -} +} diff --git a/apps/openmw/mwmechanics/aifollow.hpp b/apps/openmw/mwmechanics/aifollow.hpp index 39df024e4..9d77b903d 100644 --- a/apps/openmw/mwmechanics/aifollow.hpp +++ b/apps/openmw/mwmechanics/aifollow.hpp @@ -3,6 +3,8 @@ #include "aipackage.hpp" #include +#include "pathfinding.hpp" +#include "../../../components/esm/defs.hpp" namespace MWMechanics { @@ -17,6 +19,8 @@ namespace MWMechanics ///< \return Package completed? virtual int getTypeId() const; + std::string getFollowedActor(); + private: float mDuration; float mX; @@ -24,6 +28,14 @@ namespace MWMechanics float mZ; std::string mActorId; std::string mCellId; + + float mTimer; + float mStuckTimer; + float mTotalTime; + + ESM::Position mStuckPos; + + PathFinder mPathFinder; }; } #endif diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 73caa6ca7..989d9c6a2 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -124,6 +124,14 @@ void MWMechanics::AiSequence::queue (const AiPackage& package) mPackages.push_back (package.clone()); } +MWMechanics::AiPackage* MWMechanics::AiSequence::getActivePackage() +{ + if(mPackages.empty()) + throw std::runtime_error(std::string("No AI Package!")); + else + return mPackages.front(); +} + void MWMechanics::AiSequence::fill(const ESM::AIPackageList &list) { for (std::vector::const_iterator it = list.mList.begin(); it != list.mList.end(); ++it) diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index d65c31616..351e04480 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -59,6 +59,9 @@ namespace MWMechanics ///< Add \a package to the end of the sequence (executed after all other packages have been /// completed) + AiPackage* getActivePackage(); + ///< return the current active package. If there is no active package, throw an exeption + void fill (const ESM::AIPackageList& list); }; } diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index eee3c04b9..9a1b98d8f 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -61,20 +61,11 @@ namespace MWMechanics } } - const ESM::Pathgrid *pathgrid = world->getStore().get().search(*cell); bool cellChange = cell->mData.mX != cellX || cell->mData.mY != cellY; if(!mPathFinder.isPathConstructed() || cellChange) { cellX = cell->mData.mX; cellY = cell->mData.mY; - float xCell = 0; - float yCell = 0; - - if(cell->isExterior()) - { - xCell = cell->mData.mX * ESM::Land::REAL_SIZE; - yCell = cell->mData.mY * ESM::Land::REAL_SIZE; - } ESM::Pathgrid::Point dest; dest.mX = mX; @@ -86,7 +77,7 @@ namespace MWMechanics start.mY = pos.pos[1]; start.mZ = pos.pos[2]; - mPathFinder.buildPath(start, dest, pathgrid, xCell, yCell, true); + mPathFinder.buildPath(start, dest, actor.getCell(), true); } if(mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2])) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 77b52f5be..2f8f1cbd4 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -260,7 +260,7 @@ namespace MWMechanics start.mY = pos.pos[1]; start.mZ = pos.pos[2]; - mPathFinder.buildPath(start, dest, mPathgrid, mXCell, mYCell, false); + mPathFinder.buildPath(start, dest, actor.getCell(), false); if(mPathFinder.isPathConstructed()) { diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index a0183e973..fc9ea08b9 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -956,4 +956,9 @@ namespace MWMechanics mActors.getObjectsInRange(position, radius, objects); mObjects.getObjectsInRange(position, radius, objects); } + + std::list MechanicsManager::getActorsFollowing(const MWWorld::Ptr& actor) + { + return mActors.getActorsFollowing(actor); + } } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 012de2e32..4cfbd0134 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -136,7 +136,9 @@ namespace MWMechanics virtual void toggleAI(); virtual bool isAIActive(); - virtual void getObjectsInRange (const Ogre::Vector3& position, float radius, std::vector& objects); + virtual void getObjectsInRange (const Ogre::Vector3& position, float radius, std::vector& objects); + + virtual std::list getActorsFollowing(const MWWorld::Ptr& actor); }; } diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 9ae6263e0..70fa197d0 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -7,30 +7,10 @@ #include "OgreVector3.h" -#include -#include +#include namespace { - struct found_path {}; - - typedef boost::adjacency_list< boost::vecS, boost::vecS, boost::undirectedS, - boost::property, boost::property > - PathGridGraph; - typedef boost::property_map::type WeightMap; - typedef PathGridGraph::vertex_descriptor PointID; - typedef PathGridGraph::edge_descriptor PointConnectionID; - - class goalVisited : public boost::default_dijkstra_visitor - { - public: - goalVisited(PointID goal) {mGoal = goal;}; - void examine_vertex(PointID u, const PathGridGraph g) {if(u == mGoal) throw found_path();}; - - private: - PointID mGoal; - }; - float distanceZCorrected(ESM::Pathgrid::Point point, float x, float y, float z) { x -= point.mX; @@ -82,64 +62,104 @@ namespace return closestIndex; } - PathGridGraph buildGraph(const ESM::Pathgrid* pathgrid, float xCell = 0, float yCell = 0) + /*std::list reconstructPath(const std::vector& graph,const ESM::Pathgrid* pathgrid, int lastNode,float xCell, float yCell) { - PathGridGraph graph; - - for(unsigned int counter = 0; counter < pathgrid->mPoints.size(); counter++) + std::list path; + while(graph[lastNode].parent != -1) { - PointID pID = boost::add_vertex(graph); - graph[pID].mX = pathgrid->mPoints[counter].mX + xCell; - graph[pID].mY = pathgrid->mPoints[counter].mY + yCell; - graph[pID].mZ = pathgrid->mPoints[counter].mZ; + //std::cout << "not empty" << xCell; + ESM::Pathgrid::Point pt = pathgrid->mPoints[lastNode]; + pt.mX += xCell; + pt.mY += yCell; + path.push_front(pt); + lastNode = graph[lastNode].parent; } + return path; + }*/ - for(unsigned int counterTwo = 0; counterTwo < pathgrid->mEdges.size(); counterTwo++) - { - PointID u = pathgrid->mEdges[counterTwo].mV0; - PointID v = pathgrid->mEdges[counterTwo].mV1; - PointConnectionID edge; - bool done; - boost::tie(edge, done) = boost::add_edge(u, v, graph); - WeightMap weightmap = boost::get(boost::edge_weight, graph); - weightmap[edge] = distance(graph[u], graph[v]); - } - return graph; - } - - std::list findPath(PointID start, PointID end, PathGridGraph graph) + /*std::list buildPath2(const ESM::Pathgrid* pathgrid,int start,int goal,float xCell = 0, float yCell = 0) { - std::vector p(boost::num_vertices(graph)); - std::vector d(boost::num_vertices(graph)); - std::list shortest_path; - - try + std::vector graph; + for(unsigned int i = 0; i < pathgrid->mPoints.size(); i++) { - boost::dijkstra_shortest_paths(graph, start, - boost::predecessor_map(&p[0]).distance_map(&d[0]).visitor(goalVisited(end))); + Node node; + node.label = i; + node.parent = -1; + graph.push_back(node); + } + for(unsigned int i = 0; i < pathgrid->mEdges.size(); i++) + { + Edge edge; + edge.destination = pathgrid->mEdges[i].mV1; + edge.cost = distance(pathgrid->mPoints[pathgrid->mEdges[i].mV0],pathgrid->mPoints[pathgrid->mEdges[i].mV1]); + graph[pathgrid->mEdges[i].mV0].edges.push_back(edge); + edge.destination = pathgrid->mEdges[i].mV0; + graph[pathgrid->mEdges[i].mV1].edges.push_back(edge); } - catch(found_path& fg) + std::vector g_score(pathgrid->mPoints.size(),-1.); + std::vector f_score(pathgrid->mPoints.size(),-1.); + + g_score[start] = 0; + f_score[start] = distance(pathgrid->mPoints[start],pathgrid->mPoints[goal]); + + std::list openset; + std::list closedset; + openset.push_back(start); + + int current = -1; + + while(!openset.empty()) { - for(PointID v = end; ; v = p[v]) + current = openset.front(); + openset.pop_front(); + + if(current == goal) break; + + closedset.push_back(current); + + for(int j = 0;jmPoints[dest],pathgrid->mPoints[goal]); + if(!isInOpenSet) + { + std::list::iterator it = openset.begin(); + for(it = openset.begin();it!= openset.end();it++) + { + if(g_score[*it]>g_score[dest]) + break; + } + openset.insert(it,dest); + } + } + } } - } - return shortest_path; - } + } + return reconstructPath(graph,pathgrid,current,xCell,yCell); + + }*/ + } namespace MWMechanics { PathFinder::PathFinder() + :mIsPathConstructed(false),mIsGraphConstructed(false) { - mIsPathConstructed = false; } void PathFinder::clearPath() @@ -149,10 +169,124 @@ namespace MWMechanics mIsPathConstructed = false; } + void PathFinder::buildPathgridGraph(const ESM::Pathgrid* pathGrid) + { + mGraph.clear(); + mGScore.resize(pathGrid->mPoints.size(),-1); + mFScore.resize(pathGrid->mPoints.size(),-1); + Node defaultNode; + defaultNode.label = -1; + defaultNode.parent = -1; + mGraph.resize(pathGrid->mPoints.size(),defaultNode); + for(unsigned int i = 0; i < pathGrid->mPoints.size(); i++) + { + Node node; + node.label = i; + node.parent = -1; + mGraph[i] = node; + } + for(unsigned int i = 0; i < pathGrid->mEdges.size(); i++) + { + Edge edge; + edge.destination = pathGrid->mEdges[i].mV1; + edge.cost = distance(pathGrid->mPoints[pathGrid->mEdges[i].mV0],pathGrid->mPoints[pathGrid->mEdges[i].mV1]); + mGraph[pathGrid->mEdges[i].mV0].edges.push_back(edge); + edge.destination = pathGrid->mEdges[i].mV0; + mGraph[pathGrid->mEdges[i].mV1].edges.push_back(edge); + } + mIsGraphConstructed = true; + } + + void PathFinder::cleanUpAStar() + { + for(int i=0;i (mGraph.size());i++) + { + mGraph[i].parent = -1; + mGScore[i] = -1; + mFScore[i] = -1; + } + } + + std::list PathFinder::aStarSearch(const ESM::Pathgrid* pathGrid,int start,int goal,float xCell, float yCell) + { + cleanUpAStar(); + mGScore[start] = 0; + mFScore[start] = distance(pathGrid->mPoints[start],pathGrid->mPoints[goal]); + + std::list openset; + std::list closedset; + openset.push_back(start); + + int current = -1; + + while(!openset.empty()) + { + current = openset.front(); + openset.pop_front(); + + if(current == goal) break; + + closedset.push_back(current); + + for(int j = 0;j (mGraph[current].edges.size());j++) + { + //int next = mGraph[current].edges[j].destination + if(std::find(closedset.begin(),closedset.end(),mGraph[current].edges[j].destination) == closedset.end()) + { + int dest = mGraph[current].edges[j].destination; + float tentative_g = mGScore[current] + mGraph[current].edges[j].cost; + bool isInOpenSet = std::find(openset.begin(),openset.end(),dest) != openset.end(); + if(!isInOpenSet + || tentative_g < mGScore[dest] ) + { + mGraph[dest].parent = current; + mGScore[dest] = tentative_g; + mFScore[dest] = tentative_g + distance(pathGrid->mPoints[dest],pathGrid->mPoints[goal]); + if(!isInOpenSet) + { + std::list::iterator it = openset.begin(); + for(it = openset.begin();it!= openset.end();it++) + { + if(mGScore[*it]>mGScore[dest]) + break; + } + openset.insert(it,dest); + } + } + } + } + + } + + std::list path; + while(mGraph[current].parent != -1) + { + //std::cout << "not empty" << xCell; + ESM::Pathgrid::Point pt = pathGrid->mPoints[current]; + pt.mX += xCell; + pt.mY += yCell; + path.push_front(pt); + current = mGraph[current].parent; + } + + if(path.empty()) + { + ESM::Pathgrid::Point pt = pathGrid->mPoints[goal]; + pt.mX += xCell; + pt.mY += yCell; + path.push_front(pt); + } + + return path; + } + void PathFinder::buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, - const ESM::Pathgrid *pathGrid, float xCell, float yCell, bool allowShortcuts) + const MWWorld::CellStore* cell, bool allowShortcuts) { mPath.clear(); + if(mCell != cell) mIsGraphConstructed = false; + mCell = cell; + if(allowShortcuts) { if(MWBase::Environment::get().getWorld()->castRay(startPoint.mX, startPoint.mY, startPoint.mZ, @@ -162,13 +296,24 @@ namespace MWMechanics if(!allowShortcuts) { + const ESM::Pathgrid *pathGrid = + MWBase::Environment::get().getWorld()->getStore().get().search(*mCell->mCell); + float xCell = 0; + float yCell = 0; + + if (mCell->isExterior()) + { + xCell = mCell->mCell->mData.mX * ESM::Land::REAL_SIZE; + yCell = mCell->mCell->mData.mY * ESM::Land::REAL_SIZE; + } int startNode = getClosestPoint(pathGrid, startPoint.mX - xCell, startPoint.mY - yCell,startPoint.mZ); int endNode = getClosestPoint(pathGrid, endPoint.mX - xCell, endPoint.mY - yCell, endPoint.mZ); if(startNode != -1 && endNode != -1) { - PathGridGraph graph = buildGraph(pathGrid, xCell, yCell); - mPath = findPath(startNode, endNode, graph); + if(!mIsGraphConstructed) buildPathgridGraph(pathGrid); + + mPath = aStarSearch(pathGrid,startNode,endNode,xCell,yCell);//findPath(startNode, endNode, mGraph); if(!mPath.empty()) { diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 6f836dac7..a3ac22012 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -3,6 +3,12 @@ #include #include +#include + +namespace MWWorld +{ + class CellStore; +} namespace MWMechanics { @@ -12,9 +18,11 @@ namespace MWMechanics PathFinder(); void clearPath(); + + void buildPathgridGraph(const ESM::Pathgrid* pathGrid); + void buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, - const ESM::Pathgrid* pathGrid, float xCell = 0, float yCell = 0, - bool allowShortcuts = true); + const MWWorld::CellStore* cell, bool allowShortcuts = true); bool checkPathCompleted(float x, float y, float z); ///< \Returns true if the last point of the path has been reached. @@ -44,9 +52,38 @@ namespace MWMechanics //This functions deletes that point. void syncStart(const std::list &path); + void addPointToPath(ESM::Pathgrid::Point &point) + { + mPath.push_back(point); + } + private: - std::list mPath; + + struct Edge + { + int destination; + float cost; + }; + struct Node + { + int label; + std::vector edges; + int parent;//used in pathfinding + }; + + std::vector mGScore; + std::vector mFScore; + + std::list aStarSearch(const ESM::Pathgrid* pathGrid,int start,int goal,float xCell = 0, float yCell = 0); + void cleanUpAStar(); + + std::vector mGraph; bool mIsPathConstructed; + + + std::list mPath; + bool mIsGraphConstructed; + const MWWorld::CellStore* mCell; }; } diff --git a/apps/openmw/mwworld/actionteleport.cpp b/apps/openmw/mwworld/actionteleport.cpp index 773fde81e..b4c572ba9 100644 --- a/apps/openmw/mwworld/actionteleport.cpp +++ b/apps/openmw/mwworld/actionteleport.cpp @@ -3,6 +3,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "player.hpp" namespace MWWorld @@ -16,11 +17,35 @@ namespace MWWorld void ActionTeleport::executeImp (const Ptr& actor) { MWBase::World* world = MWBase::Environment::get().getWorld(); - world->getPlayer().setTeleported(true); - if (mCellName.empty()) - world->changeToExteriorCell (mPosition); + //find any NPC that is following the actor and teleport him too + std::list followers = MWBase::Environment::get().getMechanicsManager()->getActorsFollowing(actor); + for(std::list::iterator it = followers.begin();it != followers.end();it++) + { + std::cout << "teleporting someone!" << (*it).getCellRef().mRefID; + executeImp(*it); + } + + if(actor == world->getPlayerPtr()) + { + world->getPlayer().setTeleported(true); + if (mCellName.empty()) + world->changeToExteriorCell (mPosition); + else + world->changeToInteriorCell (mCellName, mPosition); + } else - world->changeToInteriorCell (mCellName, mPosition); + { + if (mCellName.empty()) + { + int cellX; + int cellY; + world->positionToIndex(mPosition.pos[0],mPosition.pos[1],cellX,cellY); + world->moveObject(actor,*world->getExterior(cellX,cellY), + mPosition.pos[0],mPosition.pos[1],mPosition.pos[2]); + } + else + world->moveObject(actor,*world->getInterior(mCellName),mPosition.pos[0],mPosition.pos[1],mPosition.pos[2]); + } } }