diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index d8ca6c409..66c446bae 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -18,7 +18,7 @@ opencs_hdrs_noqt (model/doc opencs_units (model/world - idtable idtableproxymodel + idtable idtableproxymodel regionmap ) @@ -57,7 +57,7 @@ opencs_hdrs_noqt (view/doc opencs_units (view/world - table tablesubview scriptsubview util + table tablesubview scriptsubview util regionmapsubview ) opencs_units_noqt (view/world diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 392175442..d54b3ac16 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -11,6 +11,7 @@ #include "idtable.hpp" #include "columns.hpp" +#include "regionmap.hpp" void CSMWorld::Data::addModel (QAbstractItemModel *model, UniversalId::Type type1, UniversalId::Type type2) @@ -304,7 +305,20 @@ QAbstractItemModel *CSMWorld::Data::getTableModel (const UniversalId& id) std::map::iterator iter = mModelIndex.find (id.getType()); if (iter==mModelIndex.end()) + { + // try creating missing (secondary) tables on the fly + // + // Note: We create these tables here so we don't have to deal with them during load/initial + // construction of the ESX data where no update signals are available. + if (id.getType()==UniversalId::Type_RegionMap) + { + RegionMap *table = 0; + addModel (table = new RegionMap (*this), UniversalId::Type_RegionMap, + UniversalId::Type_None); + return table; + } throw std::logic_error ("No table model available for " + id.toString()); + } return iter->second; } diff --git a/apps/opencs/model/world/idcollection.hpp b/apps/opencs/model/world/idcollection.hpp index e0097747c..04e65eea7 100644 --- a/apps/opencs/model/world/idcollection.hpp +++ b/apps/opencs/model/world/idcollection.hpp @@ -47,7 +47,7 @@ namespace CSMWorld { Record record = Collection::getRecord (index); record.mState = RecordBase::State_Deleted; - setRecord (index, record); + this->setRecord (index, record); } } else @@ -56,7 +56,7 @@ namespace CSMWorld IdAccessorT().getId (record) = id; record.load (reader); - int index = searchId (IdAccessorT().getId (record)); + int index = this->searchId (IdAccessorT().getId (record)); if (index==-1) { @@ -65,7 +65,7 @@ namespace CSMWorld record2.mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; (base ? record2.mBase : record2.mModified) = record; - appendRecord (record2); + this->appendRecord (record2); } else { @@ -77,7 +77,7 @@ namespace CSMWorld else record2.setModified (record); - setRecord (index, record2); + this->setRecord (index, record2); } } } diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp index fd191ba13..6a1e8045b 100644 --- a/apps/opencs/model/world/refcollection.cpp +++ b/apps/opencs/model/world/refcollection.cpp @@ -16,8 +16,6 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool Cell& cell2 = base ? cell.mBase : cell.mModified; - cell2.restore (reader, 0); /// \todo fix the index - CellRef ref; while (cell2.getNextRef (reader, ref)) diff --git a/apps/opencs/model/world/regionmap.cpp b/apps/opencs/model/world/regionmap.cpp new file mode 100644 index 000000000..697c5f450 --- /dev/null +++ b/apps/opencs/model/world/regionmap.cpp @@ -0,0 +1,513 @@ + +#include "regionmap.hpp" + +#include + +#include + +#include + +#include "data.hpp" +#include "universalid.hpp" + +CSMWorld::RegionMap::CellDescription::CellDescription() : mDeleted (false) {} + +CSMWorld::RegionMap::CellDescription::CellDescription (const Record& cell) +{ + const Cell& cell2 = cell.get(); + + if (!cell2.isExterior()) + throw std::logic_error ("Interior cell in region map"); + + mDeleted = cell.isDeleted(); + + mRegion = cell2.mRegion; + mName = cell2.mName; +} + +CSMWorld::RegionMap::CellIndex CSMWorld::RegionMap::getIndex (const QModelIndex& index) const +{ + return CellIndex (index.column()+mMin.first, + (mMax.second-mMin.second - index.row()-1)+mMin.second); +} + +QModelIndex CSMWorld::RegionMap::getIndex (const CellIndex& index) const +{ + // I hate you, Qt API naming scheme! + return QAbstractTableModel::index (mMax.second-mMin.second - (index.second-mMin.second)-1, + index.first-mMin.first); +} + +void CSMWorld::RegionMap::buildRegions() +{ + const IdCollection& regions = mData.getRegions(); + + int size = regions.getSize(); + + for (int i=0; i& region = regions.getRecord (i); + + if (!region.isDeleted()) + mColours.insert (std::make_pair (Misc::StringUtils::lowerCase (region.get().mId), + region.get().mMapColor)); + } +} + +void CSMWorld::RegionMap::buildMap() +{ + const IdCollection& cells = mData.getCells(); + + int size = cells.getSize(); + + for (int i=0; i& cell = cells.getRecord (i); + + const Cell& cell2 = cell.get(); + + if (cell2.isExterior()) + { + CellDescription description (cell); + + CellIndex index (cell2.mData.mX, cell2.mData.mY); + + mMap.insert (std::make_pair (index, description)); + } + } + + std::pair mapSize = getSize(); + + mMin = mapSize.first; + mMax = mapSize.second; +} + +void CSMWorld::RegionMap::addCell (const CellIndex& index, const CellDescription& description) +{ + std::map::iterator cell = mMap.find (index); + + if (cell!=mMap.end()) + { + cell->second = description; + } + else + { + updateSize(); + + mMap.insert (std::make_pair (index, description)); + } + + QModelIndex index2 = getIndex (index); + + dataChanged (index2, index2); +} + +void CSMWorld::RegionMap::addCells (int start, int end) +{ + const IdCollection& cells = mData.getCells(); + + for (int i=start; i<=end; ++i) + { + const Record& cell = cells.getRecord (i); + + const Cell& cell2 = cell.get(); + + if (cell2.isExterior()) + { + CellIndex index (cell2.mData.mX, cell2.mData.mY); + + CellDescription description (cell); + + addCell (index, description); + } + } +} + +void CSMWorld::RegionMap::removeCell (const CellIndex& index) +{ + std::map::iterator cell = mMap.find (index); + + if (cell!=mMap.end()) + { + mMap.erase (cell); + + QModelIndex index2 = getIndex (index); + + dataChanged (index2, index2); + + updateSize(); + } +} + +void CSMWorld::RegionMap::addRegion (const std::string& region, unsigned int colour) +{ + mColours[Misc::StringUtils::lowerCase (region)] = colour; +} + +void CSMWorld::RegionMap::removeRegion (const std::string& region) +{ + std::map::iterator iter ( + mColours.find (Misc::StringUtils::lowerCase (region))); + + if (iter!=mColours.end()) + mColours.erase (iter); +} + +void CSMWorld::RegionMap::updateRegions (const std::vector& regions) +{ + std::vector regions2 (regions); + + std::for_each (regions2.begin(), regions2.end(), &Misc::StringUtils::lowerCase); + std::sort (regions2.begin(), regions2.end()); + + for (std::map::const_iterator iter (mMap.begin()); + iter!=mMap.end(); ++iter) + { + if (!iter->second.mRegion.empty() && + std::find (regions2.begin(), regions2.end(), + Misc::StringUtils::lowerCase (iter->second.mRegion))!=regions2.end()) + { + QModelIndex index = getIndex (iter->first); + + dataChanged (index, index); + } + } +} + +void CSMWorld::RegionMap::updateSize() +{ + std::pair size = getSize(); + + { + int diff = size.first.first - mMin.first; + + if (diff<0) + { + beginInsertColumns (QModelIndex(), 0, -diff-1); + mMin.first = size.first.first; + endInsertColumns(); + } + else if (diff>0) + { + beginRemoveColumns (QModelIndex(), 0, diff-1); + mMin.first = size.first.first; + endRemoveColumns(); + } + } + + { + int diff = size.first.second - mMin.second; + + if (diff<0) + { + beginInsertRows (QModelIndex(), 0, -diff-1); + mMin.second = size.first.second; + endInsertRows(); + } + else if (diff>0) + { + beginRemoveRows (QModelIndex(), 0, diff-1); + mMin.second = size.first.second; + endRemoveRows(); + } + } + + { + int diff = size.second.first - mMax.first; + + if (diff>0) + { + int columns = columnCount(); + beginInsertColumns (QModelIndex(), columns, columns+diff-1); + mMax.first = size.second.first; + endInsertColumns(); + } + else if (diff<0) + { + int columns = columnCount(); + beginRemoveColumns (QModelIndex(), columns+diff, columns-1); + mMax.first = size.second.first; + endRemoveColumns(); + } + } + + { + int diff = size.second.second - mMax.second; + + if (diff>0) + { + int rows = rowCount(); + beginInsertRows (QModelIndex(), rows, rows+diff-1); + mMax.second = size.second.second; + endInsertRows(); + } + else if (diff<0) + { + int rows = rowCount(); + beginRemoveRows (QModelIndex(), rows+diff, rows-1); + mMax.second = size.second.second; + endRemoveRows(); + } + } +} + +std::pair CSMWorld::RegionMap::getSize() + const +{ + const IdCollection& cells = mData.getCells(); + + int size = cells.getSize(); + + CellIndex min (0, 0); + CellIndex max (0, 0); + + for (int i=0; i& cell = cells.getRecord (i); + + const Cell& cell2 = cell.get(); + + if (cell2.isExterior()) + { + CellIndex index (cell2.mData.mX, cell2.mData.mY); + + if (min==max) + { + min = index; + max = std::make_pair (min.first+1, min.second+1); + } + else + { + if (index.first=max.first) + max.first = index.first + 1; + + if (index.second=max.second) + max.second = index.second + 1; + } + } + } + + return std::make_pair (min, max); +} + +CSMWorld::RegionMap::RegionMap (Data& data) : mData (data) +{ + buildRegions(); + buildMap(); + + QAbstractItemModel *regions = data.getTableModel (UniversalId (UniversalId::Type_Regions)); + + connect (regions, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), + this, SLOT (regionsAboutToBeRemoved (const QModelIndex&, int, int))); + connect (regions, SIGNAL (rowsInserted (const QModelIndex&, int, int)), + this, SLOT (regionsInserted (const QModelIndex&, int, int))); + connect (regions, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + this, SLOT (regionsChanged (const QModelIndex&, const QModelIndex&))); + + QAbstractItemModel *cells = data.getTableModel (UniversalId (UniversalId::Type_Cells)); + + connect (cells, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), + this, SLOT (cellsAboutToBeRemoved (const QModelIndex&, int, int))); + connect (cells, SIGNAL (rowsInserted (const QModelIndex&, int, int)), + this, SLOT (cellsInserted (const QModelIndex&, int, int))); + connect (cells, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), + this, SLOT (cellsChanged (const QModelIndex&, const QModelIndex&))); +} + +int CSMWorld::RegionMap::rowCount (const QModelIndex& parent) const +{ + if (parent.isValid()) + return 0; + + return mMax.second-mMin.second; +} + +int CSMWorld::RegionMap::columnCount (const QModelIndex& parent) const +{ + if (parent.isValid()) + return 0; + + return mMax.first-mMin.first; +} + +QVariant CSMWorld::RegionMap::data (const QModelIndex& index, int role) const +{ + if (role==Qt::SizeHintRole) + return QSize (16, 16); + + if (role==Qt::BackgroundRole) + { + /// \todo GUI class in non-GUI code. Needs to be addressed eventually. + + std::map::const_iterator cell = + mMap.find (getIndex (index)); + + if (cell!=mMap.end()) + { + if (cell->second.mDeleted) + return QBrush (Qt::red, Qt::DiagCrossPattern); + + std::map::const_iterator iter = + mColours.find (Misc::StringUtils::lowerCase (cell->second.mRegion)); + + if (iter!=mColours.end()) + return QBrush ( + QColor (iter->second>>24, (iter->second>>16) & 255, (iter->second>>8) & 255, + iter->second & 255)); + + if (cell->second.mRegion.empty()) + return QBrush (Qt::Dense6Pattern); // no region + + return QBrush (Qt::red, Qt::Dense6Pattern); // invalid region + } + + return QBrush (Qt::DiagCrossPattern); + } + + if (role==Qt::ToolTipRole) + { + CellIndex cellIndex = getIndex (index); + + std::ostringstream stream; + + stream << cellIndex.first << ", " << cellIndex.second; + + std::map::const_iterator cell = + mMap.find (cellIndex); + + if (cell!=mMap.end()) + { + if (!cell->second.mName.empty()) + stream << " " << cell->second.mName; + + if (cell->second.mDeleted) + stream << " (deleted)"; + + if (!cell->second.mRegion.empty()) + { + stream << "
"; + + std::map::const_iterator iter = + mColours.find (Misc::StringUtils::lowerCase (cell->second.mRegion)); + + if (iter!=mColours.end()) + stream << cell->second.mRegion; + else + stream << "" << cell->second.mRegion << ""; + } + } + else + stream << " (no cell)"; + + return QString::fromUtf8 (stream.str().c_str()); + } + + return QVariant(); +} + +Qt::ItemFlags CSMWorld::RegionMap::flags (const QModelIndex& index) const +{ + if (mMap.find (getIndex (index))!=mMap.end()) + return Qt::ItemIsSelectable | Qt::ItemIsEnabled; + + return 0; +} + +void CSMWorld::RegionMap::regionsAboutToBeRemoved (const QModelIndex& parent, int start, int end) +{ + std::vector update; + + const IdCollection& regions = mData.getRegions(); + + for (int i=start; i<=end; ++i) + { + const Record& region = regions.getRecord (i); + + update.push_back (region.get().mId); + + removeRegion (region.get().mId); + } + + updateRegions (update); +} + +void CSMWorld::RegionMap::regionsInserted (const QModelIndex& parent, int start, int end) +{ + std::vector update; + + const IdCollection& regions = mData.getRegions(); + + for (int i=start; i<=end; ++i) + { + const Record& region = regions.getRecord (i); + + if (!region.isDeleted()) + { + update.push_back (region.get().mId); + + addRegion (region.get().mId, region.get().mMapColor); + } + } + + updateRegions (update); +} + +void CSMWorld::RegionMap::regionsChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) +{ + // Note: At this point an additional check could be inserted to see if there is any change to the + // columns we are interested in. If not we can exit the function here and avoid all updating. + + std::vector update; + + const IdCollection& regions = mData.getRegions(); + + for (int i=topLeft.row(); i<=bottomRight.column(); ++i) + { + const Record& region = regions.getRecord (i); + + update.push_back (region.get().mId); + + if (!region.isDeleted()) + addRegion (region.get().mId, region.get().mMapColor); + else + removeRegion (region.get().mId); + } + + updateRegions (update); +} + +void CSMWorld::RegionMap::cellsAboutToBeRemoved (const QModelIndex& parent, int start, int end) +{ + const IdCollection& cells = mData.getCells(); + + for (int i=start; i<=end; ++i) + { + const Record& cell = cells.getRecord (i); + + const Cell& cell2 = cell.get(); + + if (cell2.isExterior()) + { + CellIndex index (cell2.mData.mX, cell2.mData.mY); + + removeCell (index); + } + } +} + +void CSMWorld::RegionMap::cellsInserted (const QModelIndex& parent, int start, int end) +{ + addCells (start, end); +} + +void CSMWorld::RegionMap::cellsChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) +{ + // Note: At this point an additional check could be inserted to see if there is any change to the + // columns we are interested in. If not we can exit the function here and avoid all updating. + + addCells (topLeft.row(), bottomRight.row()); +} \ No newline at end of file diff --git a/apps/opencs/model/world/regionmap.hpp b/apps/opencs/model/world/regionmap.hpp new file mode 100644 index 000000000..7fb89f20a --- /dev/null +++ b/apps/opencs/model/world/regionmap.hpp @@ -0,0 +1,111 @@ +#ifndef CSM_WOLRD_REGIONMAP_H +#define CSM_WOLRD_REGIONMAP_H + +#include +#include +#include + +#include + +#include "record.hpp" +#include "cell.hpp" + +namespace CSMWorld +{ + class Data; + + /// \brief Model for the region map + /// + /// This class does not holds any record data (other than for the purpose of buffering). + class RegionMap : public QAbstractTableModel + { + Q_OBJECT + + public: + + typedef std::pair CellIndex; + + private: + + struct CellDescription + { + bool mDeleted; + std::string mRegion; + std::string mName; + + CellDescription(); + + CellDescription (const Record& cell); + }; + + Data& mData; + std::map mMap; + CellIndex mMin; ///< inclusive + CellIndex mMax; ///< exclusive + std::map mColours; ///< region ID, colour (RGBA) + + CellIndex getIndex (const QModelIndex& index) const; + ///< Translates a Qt model index into a cell index (which can contain negative components) + + QModelIndex getIndex (const CellIndex& index) const; + + void buildRegions(); + + void buildMap(); + + void addCell (const CellIndex& index, const CellDescription& description); + ///< May be called on a cell that is already in the map (in which case an update is + // performed) + + void addCells (int start, int end); + + void removeCell (const CellIndex& index); + ///< May be called on a cell that is not in the map (in which case the call is ignored) + + void addRegion (const std::string& region, unsigned int colour); + ///< May be called on a region that is already listed (in which case an update is + /// performed) + /// + /// \note This function does not update the region map. + + void removeRegion (const std::string& region); + ///< May be called on a region that is not listed (in which case the call is ignored) + /// + /// \note This function does not update the region map. + + void updateRegions (const std::vector& regions); + ///< Update cells affected by the listed regions + + void updateSize(); + + std::pair getSize() const; + + public: + + RegionMap (Data& data); + + virtual int rowCount (const QModelIndex& parent = QModelIndex()) const; + + virtual int columnCount (const QModelIndex& parent = QModelIndex()) const; + + virtual QVariant data (const QModelIndex& index, int role = Qt::DisplayRole) const; + + virtual Qt::ItemFlags flags (const QModelIndex& index) const; + + private slots: + + void regionsAboutToBeRemoved (const QModelIndex& parent, int start, int end); + + void regionsInserted (const QModelIndex& parent, int start, int end); + + void regionsChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + + void cellsAboutToBeRemoved (const QModelIndex& parent, int start, int end); + + void cellsInserted (const QModelIndex& parent, int start, int end); + + void cellsChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + }; +} + +#endif diff --git a/apps/opencs/model/world/universalid.cpp b/apps/opencs/model/world/universalid.cpp index ef32681a0..c66101950 100644 --- a/apps/opencs/model/world/universalid.cpp +++ b/apps/opencs/model/world/universalid.cpp @@ -33,6 +33,8 @@ namespace "Referenceables" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_References, "References" }, + { CSMWorld::UniversalId::Class_NonRecord, CSMWorld::UniversalId::Type_RegionMap, + "Region Map" }, { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, 0 } // end marker }; diff --git a/apps/opencs/model/world/universalid.hpp b/apps/opencs/model/world/universalid.hpp index 9fae0a9f4..453da3ef3 100644 --- a/apps/opencs/model/world/universalid.hpp +++ b/apps/opencs/model/world/universalid.hpp @@ -81,7 +81,8 @@ namespace CSMWorld Type_Static, Type_Weapon, Type_References, - Type_Reference + Type_Reference, + Type_RegionMap }; private: diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index 43f068042..6425797a1 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -139,6 +139,10 @@ void CSVDoc::View::setupWorldMenu() QAction *references = new QAction (tr ("References"), this); connect (references, SIGNAL (triggered()), this, SLOT (addReferencesSubView())); world->addAction (references); + + QAction *regionMap = new QAction (tr ("Region Map"), this); + connect (regionMap, SIGNAL (triggered()), this, SLOT (addRegionMapSubView())); + world->addAction (regionMap); } void CSVDoc::View::setupUi() @@ -362,6 +366,11 @@ void CSVDoc::View::addReferencesSubView() addSubView (CSMWorld::UniversalId::Type_References); } +void CSVDoc::View::addRegionMapSubView() +{ + addSubView (CSMWorld::UniversalId::Type_RegionMap); +} + void CSVDoc::View::abortOperation (int type) { mDocument->abortOperation (type); diff --git a/apps/opencs/view/doc/view.hpp b/apps/opencs/view/doc/view.hpp index b3c4ae22a..ff81f8223 100644 --- a/apps/opencs/view/doc/view.hpp +++ b/apps/opencs/view/doc/view.hpp @@ -151,6 +151,8 @@ namespace CSVDoc void addReferencesSubView(); + void addRegionMapSubView(); + void showUserSettings(); }; } diff --git a/apps/opencs/view/world/regionmapsubview.cpp b/apps/opencs/view/world/regionmapsubview.cpp new file mode 100644 index 000000000..b82c1afb5 --- /dev/null +++ b/apps/opencs/view/world/regionmapsubview.cpp @@ -0,0 +1,29 @@ + +#include "regionmapsubview.hpp" + +#include +#include + +CSVWorld::RegionMapSubView::RegionMapSubView (CSMWorld::UniversalId universalId, + CSMDoc::Document& document) +: CSVDoc::SubView (universalId) +{ + mTable = new QTableView (this); + + mTable->verticalHeader()->hide(); + mTable->horizontalHeader()->hide(); + + mTable->setSelectionMode (QAbstractItemView::ExtendedSelection); + + mTable->setModel (document.getData().getTableModel (universalId)); + + mTable->resizeColumnsToContents(); + mTable->resizeRowsToContents(); + + setWidget (mTable); +} + +void CSVWorld::RegionMapSubView::setEditLock (bool locked) +{ + +} \ No newline at end of file diff --git a/apps/opencs/view/world/regionmapsubview.hpp b/apps/opencs/view/world/regionmapsubview.hpp new file mode 100644 index 000000000..1655107af --- /dev/null +++ b/apps/opencs/view/world/regionmapsubview.hpp @@ -0,0 +1,27 @@ +#ifndef CSV_WORLD_REGIONMAPSUBVIEW_H +#define CSV_WORLD_REGIONMAPSUBVIEW_H + +#include "../doc/subview.hpp" + +class QTableView; + +namespace CSMDoc +{ + class Document; +} + +namespace CSVWorld +{ + class RegionMapSubView : public CSVDoc::SubView + { + QTableView *mTable; + + public: + + RegionMapSubView (CSMWorld::UniversalId universalId, CSMDoc::Document& document); + + virtual void setEditLock (bool locked); + }; +} + +#endif diff --git a/apps/opencs/view/world/subviews.cpp b/apps/opencs/view/world/subviews.cpp index cd98ed4e0..1c06ea2f6 100644 --- a/apps/opencs/view/world/subviews.cpp +++ b/apps/opencs/view/world/subviews.cpp @@ -6,6 +6,7 @@ #include "tablesubview.hpp" #include "dialoguesubview.hpp" #include "scriptsubview.hpp" +#include "regionmapsubview.hpp" void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) { @@ -38,6 +39,8 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) manager.add (CSMWorld::UniversalId::Type_Script, new CSVDoc::SubViewFactory); + manager.add (CSMWorld::UniversalId::Type_RegionMap, new CSVDoc::SubViewFactory); + // manager.add (CSMWorld::UniversalId::Type_Global, // new CSVDoc::SubViewFactoryWithCreateFlag (true)); } \ No newline at end of file diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index ccc9d1de6..d88eec71b 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -215,4 +215,27 @@ namespace MWClass ptr.get(); return ref->mBase->mData.mWeight; } + + std::pair Light::canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const + { + MWWorld::InventoryStore& invStore = MWWorld::Class::get(npc).getInventoryStore(npc); + MWWorld::ContainerStoreIterator weapon = invStore.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + + if(weapon == invStore.end()) + return std::make_pair(1,""); + + /// \todo the 2h check is repeated many times; put it in a function + if(weapon->getTypeName() == typeid(ESM::Weapon).name() && + (weapon->get()->mBase->mData.mType == ESM::Weapon::LongBladeTwoHand || + weapon->get()->mBase->mData.mType == ESM::Weapon::BluntTwoClose || + weapon->get()->mBase->mData.mType == ESM::Weapon::BluntTwoWide || + weapon->get()->mBase->mData.mType == ESM::Weapon::SpearTwoWide || + weapon->get()->mBase->mData.mType == ESM::Weapon::AxeTwoHand || + weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanBow || + weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow)) + { + return std::make_pair(3,""); + } + return std::make_pair(1,""); + } } diff --git a/apps/openmw/mwclass/light.hpp b/apps/openmw/mwclass/light.hpp index 7f747ef48..79d662763 100644 --- a/apps/openmw/mwclass/light.hpp +++ b/apps/openmw/mwclass/light.hpp @@ -61,6 +61,8 @@ namespace MWClass virtual float getWeight (const MWWorld::Ptr& ptr) const; virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; + + std::pair canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const; }; } diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp index f438d5e09..3bc0de4cb 100644 --- a/apps/openmw/mwgui/console.cpp +++ b/apps/openmw/mwgui/console.cpp @@ -101,59 +101,58 @@ namespace MWGui } Console::Console(int w, int h, bool consoleOnlyScripts) - : Layout("openmw_console.layout"), + : WindowBase("openmw_console.layout"), mCompilerContext (MWScript::CompilerContext::Type_Console), mConsoleOnlyScripts (consoleOnlyScripts) { setCoord(10,10, w-10, h/2); - getWidget(command, "edit_Command"); - getWidget(history, "list_History"); + getWidget(mCommandLine, "edit_Command"); + getWidget(mHistory, "list_History"); // Set up the command line box - command->eventEditSelectAccept += + mCommandLine->eventEditSelectAccept += newDelegate(this, &Console::acceptCommand); - command->eventKeyButtonPressed += + mCommandLine->eventKeyButtonPressed += newDelegate(this, &Console::keyPress); // Set up the log window - history->setOverflowToTheLeft(true); - history->setEditStatic(true); - history->setVisibleVScroll(true); + mHistory->setOverflowToTheLeft(true); + mHistory->setEditStatic(true); + mHistory->setVisibleVScroll(true); // compiler MWScript::registerExtensions (mExtensions, mConsoleOnlyScripts); mCompilerContext.setExtensions (&mExtensions); } - void Console::enable() + void Console::open() { - setVisible(true); - // Give keyboard focus to the combo box whenever the console is // turned on - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(command); + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCommandLine); } - void Console::disable() + void Console::close() { - setVisible(false); + // Apparently, hidden widgets can retain key focus + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(NULL); } void Console::setFont(const std::string &fntName) { - history->setFontName(fntName); - command->setFontName(fntName); + mHistory->setFontName(fntName); + mCommandLine->setFontName(fntName); } void Console::clearHistory() { - history->setCaption(""); + mHistory->setCaption(""); } void Console::print(const std::string &msg) { - history->addText(msg); + mHistory->addText(msg); } void Console::printOK(const std::string &msg) @@ -215,7 +214,7 @@ namespace MWGui { std::vector matches; listNames(); - command->setCaption(complete( command->getCaption(), matches )); + mCommandLine->setCaption(complete( mCommandLine->getCaption(), matches )); #if 0 int i = 0; for(std::vector::iterator it=matches.begin(); it < matches.end(); it++,i++ ) @@ -227,50 +226,50 @@ namespace MWGui #endif } - if(command_history.empty()) return; + if(mCommandHistory.empty()) return; // Traverse history with up and down arrows if(key == MyGUI::KeyCode::ArrowUp) { // If the user was editing a string, store it for later - if(current == command_history.end()) - editString = command->getCaption(); + if(mCurrent == mCommandHistory.end()) + mEditString = mCommandLine->getCaption(); - if(current != command_history.begin()) + if(mCurrent != mCommandHistory.begin()) { - current--; - command->setCaption(*current); + mCurrent--; + mCommandLine->setCaption(*mCurrent); } } else if(key == MyGUI::KeyCode::ArrowDown) { - if(current != command_history.end()) + if(mCurrent != mCommandHistory.end()) { - current++; + mCurrent++; - if(current != command_history.end()) - command->setCaption(*current); + if(mCurrent != mCommandHistory.end()) + mCommandLine->setCaption(*mCurrent); else // Restore the edit string - command->setCaption(editString); + mCommandLine->setCaption(mEditString); } } } void Console::acceptCommand(MyGUI::EditBox* _sender) { - const std::string &cm = command->getCaption(); + const std::string &cm = mCommandLine->getCaption(); if(cm.empty()) return; // Add the command to the history, and set the current pointer to // the end of the list - command_history.push_back(cm); - current = command_history.end(); - editString.clear(); + mCommandHistory.push_back(cm); + mCurrent = mCommandHistory.end(); + mEditString.clear(); execute (cm); - command->setCaption(""); + mCommandLine->setCaption(""); } std::string Console::complete( std::string input, std::vector &matches ) @@ -412,7 +411,7 @@ namespace MWGui setTitle("#{sConsoleTitle}"); mPtr = MWWorld::Ptr(); } - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(command); + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCommandLine); } void Console::onReferenceUnavailable() diff --git a/apps/openmw/mwgui/console.hpp b/apps/openmw/mwgui/console.hpp index b1d961ed2..890176363 100644 --- a/apps/openmw/mwgui/console.hpp +++ b/apps/openmw/mwgui/console.hpp @@ -1,7 +1,6 @@ #ifndef MWGUI_CONSOLE_H #define MWGUI_CONSOLE_H -#include #include #include #include @@ -18,10 +17,11 @@ #include "../mwscript/interpretercontext.hpp" #include "referenceinterface.hpp" +#include "windowbase.hpp" namespace MWGui { - class Console : private OEngine::GUI::Layout, private Compiler::ErrorHandler, public ReferenceInterface + class Console : public WindowBase, private Compiler::ErrorHandler, public ReferenceInterface { private: @@ -55,21 +55,20 @@ namespace MWGui public: - MyGUI::EditBox* command; - MyGUI::EditBox* history; + MyGUI::EditBox* mCommandLine; + MyGUI::EditBox* mHistory; typedef std::list StringList; // History of previous entered commands - StringList command_history; - StringList::iterator current; - std::string editString; + StringList mCommandHistory; + StringList::iterator mCurrent; + std::string mEditString; Console(int w, int h, bool consoleOnlyScripts); - void enable(); - - void disable(); + virtual void open(); + virtual void close(); void setFont(const std::string &fntName); @@ -91,7 +90,7 @@ namespace MWGui void execute (const std::string& command); - void executeFile (const std::string& command); + void executeFile (const std::string& path); private: diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 5dbdbf90a..38bc0481d 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -329,7 +329,7 @@ namespace MWGui mMap->setVisible(false); mMenu->setVisible(false); mStatsWindow->setVisible(false); - mConsole->disable(); + mConsole->setVisible(false); mJournal->setVisible(false); mDialogueWindow->setVisible(false); mContainerWindow->setVisible(false); @@ -398,7 +398,7 @@ namespace MWGui mInventoryWindow->setVisible(mInventoryWindow->pinned()); mSpellWindow->setVisible(mSpellWindow->pinned()); - mConsole->enable(); + mConsole->setVisible(true); break; case GM_Scroll: mScrollWindow->setVisible(true); diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 739176a8e..b9f1a67d4 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -183,11 +183,14 @@ namespace MWInput break; case A_Activate: resetIdleTime(); - if( MWBase::Environment::get().getWindowManager()->isGuiMode()) { - // Pressing the activation key when a messagebox is prompting for "ok" will activate the ok button - MWBase::Environment::get().getWindowManager()->enterPressed(); - } - activate(); + + if (mWindows.getMode() == MWGui::GM_Container) { + toggleContainer (); + } else if (MWBase::Environment::get().getWindowManager()->isGuiMode()) { + MWBase::Environment::get().getWindowManager()->enterPressed(); + } else { + activate(); + } break; case A_Journal: toggleJournal (); @@ -678,6 +681,24 @@ namespace MWInput // .. but don't touch any other mode, except container. } + void InputManager::toggleContainer() + { + if (MyGUI::InputManager::getInstance ().isModalAny()) + return; + + bool gameMode = !mWindows.isGuiMode(); + + if(!gameMode) + { + if (mWindows.getMode() == MWGui::GM_Container) + mWindows.popGuiMode(); + else + mWindows.pushGuiMode(MWGui::GM_Container); + } + + } + + void InputManager::toggleConsole() { if (MyGUI::InputManager::getInstance ().isModalAny()) diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index f463de811..0c7940fd6 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -170,6 +170,7 @@ namespace MWInput void toggleSpell(); void toggleWeapon(); void toggleInventory(); + void toggleContainer(); void toggleConsole(); void screenshot(); void toggleJournal(); diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index e85aa9b83..7af7e063c 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -168,20 +168,20 @@ namespace MWMechanics void Actors::addActor (const MWWorld::Ptr& ptr) { // erase previous death events since we are currently only tracking them while in an active cell - MWWorld::Class::get (ptr).getCreatureStats (ptr).clearHasDied(); + MWWorld::Class::get(ptr).getCreatureStats(ptr).clearHasDied(); MWRender::Animation *anim = MWBase::Environment::get().getWorld()->getAnimation(ptr); - if(!MWWorld::Class::get(ptr).getCreatureStats(ptr).isDead()) - mActors.insert(std::make_pair(ptr, CharacterController(ptr, anim, CharState_Idle))); - else - mActors.insert(std::make_pair(ptr, CharacterController(ptr, anim, CharState_Death1))); + mActors.insert(std::make_pair(ptr, new CharacterController(ptr, anim))); } void Actors::removeActor (const MWWorld::Ptr& ptr) { PtrControllerMap::iterator iter = mActors.find(ptr); if(iter != mActors.end()) + { + delete iter->second; mActors.erase(iter); + } } void Actors::updateActor(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr) @@ -189,10 +189,10 @@ namespace MWMechanics PtrControllerMap::iterator iter = mActors.find(old); if(iter != mActors.end()) { - CharacterController ctrl = iter->second; + CharacterController *ctrl = iter->second; mActors.erase(iter); - ctrl.updatePtr(ptr); + ctrl->updatePtr(ptr); mActors.insert(std::make_pair(ptr, ctrl)); } } @@ -203,7 +203,10 @@ namespace MWMechanics while(iter != mActors.end()) { if(iter->first.getCell()==cellStore) + { + delete iter->second; mActors.erase(iter++); + } else ++iter; } @@ -222,8 +225,8 @@ namespace MWMechanics { if(!MWWorld::Class::get(iter->first).getCreatureStats(iter->first).isDead()) { - if(iter->second.getState() >= CharState_Death1) - iter->second.setState(CharState_Idle); + if(iter->second->isDead()) + iter->second->resurrect(); updateActor(iter->first, totalDuration); if(iter->first.getTypeName() == typeid(ESM::NPC).name()) @@ -250,10 +253,10 @@ namespace MWMechanics continue; } - if(iter->second.getState() >= CharState_Death1) + if(iter->second->isDead()) continue; - iter->second.setState(CharState_Death1); + iter->second->kill(); ++mDeathCount[MWWorld::Class::get(iter->first).getId(iter->first)]; @@ -270,7 +273,7 @@ namespace MWMechanics for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter) { Movement movement; - iter->second.update(duration, movement); + iter->second->update(duration, movement); mMovement.push_back(std::make_pair(iter->first, movement)); } MWBase::Environment::get().getWorld()->doPhysics(mMovement, duration); @@ -297,27 +300,27 @@ namespace MWMechanics { PtrControllerMap::iterator iter = mActors.find(ptr); if(iter != mActors.end()) - iter->second.forceStateUpdate(); + iter->second->forceStateUpdate(); } void Actors::playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number) { PtrControllerMap::iterator iter = mActors.find(ptr); if(iter != mActors.end()) - iter->second.playGroup(groupName, mode, number); + iter->second->playGroup(groupName, mode, number); } void Actors::skipAnimation(const MWWorld::Ptr& ptr) { PtrControllerMap::iterator iter = mActors.find(ptr); if(iter != mActors.end()) - iter->second.skipAnim(); + iter->second->skipAnim(); } bool Actors::checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) { PtrControllerMap::iterator iter = mActors.find(ptr); if(iter != mActors.end()) - return iter->second.isAnimPlaying(groupName); + return iter->second->isAnimPlaying(groupName); return false; } } diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 386840e3a..1369d783c 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -25,7 +25,7 @@ namespace MWMechanics { class Actors { - typedef std::map PtrControllerMap; + typedef std::map PtrControllerMap; PtrControllerMap mActors; MWWorld::PtrMovementList mMovement; diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 567201952..d8e32be48 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -39,62 +39,65 @@ namespace MWMechanics { -static const struct StateInfo { +struct StateInfo { CharacterState state; const char groupname[32]; - Priority priority; - bool loops; -} sStateList[] = { - { CharState_Idle, "idle", Priority_Default, true }, - { CharState_Idle2, "idle2", Priority_Default, true }, - { CharState_Idle3, "idle3", Priority_Default, true }, - { CharState_Idle4, "idle4", Priority_Default, true }, - { CharState_Idle5, "idle5", Priority_Default, true }, - { CharState_Idle6, "idle6", Priority_Default, true }, - { CharState_Idle7, "idle7", Priority_Default, true }, - { CharState_Idle8, "idle8", Priority_Default, true }, - { CharState_Idle9, "idle9", Priority_Default, true }, - { CharState_IdleSwim, "idleswim", Priority_Default, true }, - { CharState_IdleSneak, "idlesneak", Priority_Default, true }, +}; - { CharState_WalkForward, "walkforward", Priority_Default, true }, - { CharState_WalkBack, "walkback", Priority_Default, true }, - { CharState_WalkLeft, "walkleft", Priority_Default, true }, - { CharState_WalkRight, "walkright", Priority_Default, true }, +static const StateInfo sStateList[] = { + { CharState_Idle, "idle" }, + { CharState_Idle2, "idle2" }, + { CharState_Idle3, "idle3" }, + { CharState_Idle4, "idle4" }, + { CharState_Idle5, "idle5" }, + { CharState_Idle6, "idle6" }, + { CharState_Idle7, "idle7" }, + { CharState_Idle8, "idle8" }, + { CharState_Idle9, "idle9" }, + { CharState_IdleSwim, "idleswim" }, + { CharState_IdleSneak, "idlesneak" }, - { CharState_SwimWalkForward, "swimwalkforward", Priority_Default, true }, - { CharState_SwimWalkBack, "swimwalkback", Priority_Default, true }, - { CharState_SwimWalkLeft, "swimwalkleft", Priority_Default, true }, - { CharState_SwimWalkRight, "swimwalkright", Priority_Default, true }, - - { CharState_RunForward, "runforward", Priority_Default, true }, - { CharState_RunBack, "runback", Priority_Default, true }, - { CharState_RunLeft, "runleft", Priority_Default, true }, - { CharState_RunRight, "runright", Priority_Default, true }, - - { CharState_SwimRunForward, "swimrunforward", Priority_Default, true }, - { CharState_SwimRunBack, "swimrunback", Priority_Default, true }, - { CharState_SwimRunLeft, "swimrunleft", Priority_Default, true }, - { CharState_SwimRunRight, "swimrunright", Priority_Default, true }, - - { CharState_SneakForward, "sneakforward", Priority_Default, true }, - { CharState_SneakBack, "sneakback", Priority_Default, true }, - { CharState_SneakLeft, "sneakleft", Priority_Default, true }, - { CharState_SneakRight, "sneakright", Priority_Default, true }, - - { CharState_TurnLeft, "turnleft", Priority_Default, true }, - { CharState_TurnRight, "turnright", Priority_Default, true }, - - { CharState_Jump, "jump", Priority_Default, true }, - - { CharState_Death1, "death1", Priority_Death, false }, - { CharState_Death2, "death2", Priority_Death, false }, - { CharState_Death3, "death3", Priority_Death, false }, - { CharState_Death4, "death4", Priority_Death, false }, - { CharState_Death5, "death5", Priority_Death, false }, + { CharState_Death1, "death1" }, + { CharState_Death2, "death2" }, + { CharState_Death3, "death3" }, + { CharState_Death4, "death4" }, + { CharState_Death5, "death5" }, }; static const StateInfo *sStateListEnd = &sStateList[sizeof(sStateList)/sizeof(sStateList[0])]; + +static const StateInfo sMovementList[] = { + { CharState_WalkForward, "walkforward" }, + { CharState_WalkBack, "walkback" }, + { CharState_WalkLeft, "walkleft" }, + { CharState_WalkRight, "walkright" }, + + { CharState_SwimWalkForward, "swimwalkforward" }, + { CharState_SwimWalkBack, "swimwalkback" }, + { CharState_SwimWalkLeft, "swimwalkleft" }, + { CharState_SwimWalkRight, "swimwalkright" }, + + { CharState_RunForward, "runforward" }, + { CharState_RunBack, "runback" }, + { CharState_RunLeft, "runleft" }, + { CharState_RunRight, "runright" }, + + { CharState_SwimRunForward, "swimrunforward" }, + { CharState_SwimRunBack, "swimrunback" }, + { CharState_SwimRunLeft, "swimrunleft" }, + { CharState_SwimRunRight, "swimrunright" }, + + { CharState_SneakForward, "sneakforward" }, + { CharState_SneakBack, "sneakback" }, + { CharState_SneakLeft, "sneakleft" }, + { CharState_SneakRight, "sneakright" }, + + { CharState_TurnLeft, "turnleft" }, + { CharState_TurnRight, "turnright" }, +}; +static const StateInfo *sMovementListEnd = &sMovementList[sizeof(sMovementList)/sizeof(sMovementList[0])]; + + class FindCharState { CharacterState state; @@ -108,19 +111,18 @@ public: static const struct WeaponInfo { WeaponType type; - const char idlegroup[16]; - const char movementgroup[16]; - const char actiongroup[16]; + const char shortgroup[16]; + const char longgroup[16]; } sWeaponTypeList[] = { - { WeapType_HandToHand, "hh", "hh", "handtohand" }, - { WeapType_OneHand, "1h", "1h", "weapononehand" }, - { WeapType_TwoHand, "2c", "2c", "weapontwohand" }, - { WeapType_TwoWide, "2w", "2w", "weapontwowide" }, - { WeapType_BowAndArrow, "1h", "1h", "bowandarrow" }, - { WeapType_Crossbow, "crossbow", "1h", "crossbow" }, - { WeapType_ThowWeapon, "1h", "1h", "throwweapon" }, - { WeapType_PickProbe, "1h", "1h", "pickprobe" }, - { WeapType_Spell, "spell", "", "spellcast" }, + { WeapType_HandToHand, "hh", "handtohand" }, + { WeapType_OneHand, "1h", "weapononehand" }, + { WeapType_TwoHand, "2c", "weapontwohand" }, + { WeapType_TwoWide, "2w", "weapontwowide" }, + { WeapType_BowAndArrow, "1h", "bowandarrow" }, + { WeapType_Crossbow, "crossbow", "crossbow" }, + { WeapType_ThowWeapon, "1h", "throwweapon" }, + { WeapType_PickProbe, "1h", "pickprobe" }, + { WeapType_Spell, "spell", "spellcast" }, }; static const WeaponInfo *sWeaponTypeListEnd = &sWeaponTypeList[sizeof(sWeaponTypeList)/sizeof(sWeaponTypeList[0])]; @@ -135,31 +137,84 @@ public: }; -void CharacterController::getCurrentGroup(std::string &group, Priority &priority, bool &loops) const +void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterState movement, bool force) { - std::string name; - const StateInfo *state = std::find_if(sStateList, sStateListEnd, FindCharState(mCharState)); - if(state == sStateListEnd) - throw std::runtime_error("Failed to find character state "+Ogre::StringConverter::toString(mCharState)); + const WeaponInfo *weap = std::find_if(sWeaponTypeList, sWeaponTypeListEnd, FindWeaponType(mWeaponType)); - name = state->groupname; - priority = state->priority; - loops = state->loops; - - if(!(mCharState >= CharState_Death1) && mWeaponType != WeapType_None) + if(force || idle != mIdleState) { - const WeaponInfo *weap = std::find_if(sWeaponTypeList, sWeaponTypeListEnd, FindWeaponType(mWeaponType)); - if(weap != sWeaponTypeListEnd) + mIdleState = idle; + + std::string idle; + // Only play "idleswim" or "idlesneak" if they exist. Otherwise, fallback to + // "idle"+weapon or "idle". + if(mIdleState == CharState_IdleSwim && mAnimation->hasAnimation("idleswim")) + idle = "idleswim"; + else if(mIdleState == CharState_IdleSneak && mAnimation->hasAnimation("idlesneak")) + idle = "idlesneak"; + else { - if(mCharState == CharState_Idle) - (group=name) += weap->idlegroup; - else - (group=name) += weap->movementgroup; + idle = "idle"; + if(weap != sWeaponTypeListEnd) + { + idle += weap->shortgroup; + if(!mAnimation->hasAnimation(idle)) + idle = "idle"; + } } + + mAnimation->disable(mCurrentIdle); + mCurrentIdle = idle; + mAnimation->play(mCurrentIdle, Priority_Default, MWRender::Animation::Group_All, false, + 1.0f, "start", "stop", 0.0f, ~0ul); } - if(group.empty() || !mAnimation->hasAnimation(group)) - group = (mAnimation->hasAnimation(name) ? name : std::string()); + if(force || movement != mMovementState) + { + mMovementState = movement; + + std::string movement; + MWRender::Animation::Group movegroup = MWRender::Animation::Group_All; + const StateInfo *movestate = std::find_if(sMovementList, sMovementListEnd, FindCharState(mMovementState)); + if(movestate != sMovementListEnd) + { + movement = movestate->groupname; + if(weap != sWeaponTypeListEnd && movement.find("swim") == std::string::npos) + { + movement += weap->shortgroup; + if(!mAnimation->hasAnimation(movement)) + { + movegroup = MWRender::Animation::Group_LowerBody; + movement = movestate->groupname; + } + } + + if(!mAnimation->hasAnimation(movement)) + { + std::string::size_type sneakpos = movement.find("sneak"); + if(sneakpos == std::string::npos) + movement.clear(); + else + { + movegroup = MWRender::Animation::Group_LowerBody; + movement.erase(sneakpos, 5); + if(!mAnimation->hasAnimation(movement)) + movement.clear(); + } + } + } + + mAnimation->disable(mCurrentMovement); + mCurrentMovement = movement; + if(!mCurrentMovement.empty()) + { + float vel, speed = 0.0f; + if(mMovementSpeed > 0.0f && (vel=mAnimation->getVelocity(mCurrentMovement)) > 1.0f) + speed = mMovementSpeed / vel; + mAnimation->play(mCurrentMovement, Priority_Movement, movegroup, false, + speed, "start", "stop", 0.0f, ~0ul); + } + } } @@ -167,14 +222,17 @@ void CharacterController::getWeaponGroup(WeaponType weaptype, std::string &group { const WeaponInfo *info = std::find_if(sWeaponTypeList, sWeaponTypeListEnd, FindWeaponType(weaptype)); if(info != sWeaponTypeListEnd) - group = info->actiongroup; + group = info->longgroup; } -CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim, CharacterState state) +CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim) : mPtr(ptr) , mAnimation(anim) - , mCharState(state) + , mIdleState(CharState_Idle) + , mMovementState(CharState_None) + , mMovementSpeed(0.0f) + , mDeathState(CharState_None) , mUpperBodyState(UpperCharState_Nothing) , mWeaponType(WeapType_None) , mSkipAnim(false) @@ -190,6 +248,12 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim /* Accumulate along X/Y only for now, until we can figure out how we should * handle knockout and death which moves the character down. */ mAnimation->setAccumulation(Ogre::Vector3(1.0f, 1.0f, 0.0f)); + + if(MWWorld::Class::get(mPtr).getCreatureStats(mPtr).isDead()) + { + /* FIXME: Get the actual death state used. */ + mDeathState = CharState_Death1; + } } else { @@ -197,12 +261,17 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim mAnimation->setAccumulation(Ogre::Vector3(0.0f)); } - std::string group; - Priority prio; - bool loops; - getCurrentGroup(group, prio, loops); - mAnimation->play(group, prio, MWRender::Animation::Group_All, false, - "start", "stop", 1.0f, loops ? (~(size_t)0) : 0); + refreshCurrentAnims(mIdleState, mMovementState, true); + if(mDeathState != CharState_None) + { + const StateInfo *state = std::find_if(sStateList, sStateListEnd, FindCharState(mDeathState)); + if(state == sStateListEnd) + throw std::runtime_error("Failed to find character state "+Ogre::StringConverter::toString(mDeathState)); + + mCurrentDeath = state->groupname; + mAnimation->play(mCurrentDeath, Priority_Death, MWRender::Animation::Group_All, + false, 1.0f, "start", "stop", 1.0f, 0); + } } CharacterController::~CharacterController() @@ -219,7 +288,6 @@ void CharacterController::updatePtr(const MWWorld::Ptr &ptr) void CharacterController::update(float duration, Movement &movement) { const MWWorld::Class &cls = MWWorld::Class::get(mPtr); - float speed = 0.0f; if(!cls.isActor()) { @@ -232,32 +300,12 @@ void CharacterController::update(float duration, Movement &movement) mAnimation->play(mAnimQueue.front().first, Priority_Default, MWRender::Animation::Group_All, false, - "start", "stop", 0.0f, mAnimQueue.front().second); + 1.0f, "start", "stop", 0.0f, mAnimQueue.front().second); } } } else if(!cls.getCreatureStats(mPtr).isDead()) { - /*std::list> lastKeys = cls.getCreatureStats(mPtr).getLastAnimKey(); - for(std::list>::iterator it = cls.getCreatureStats(mPtr).getLastAnimKey().begin(); - it!=cls.getCreatureStats(mPtr).getLastAnimKey().end();) - { - std::cout << "NE"; - std::pair lastKey = *it; - if(!lastKey.first.empty()) - { - std::cout << lastKey.first << " " << lastKey.second << " "; - //const std::string &evt = key->second; - size_t off = lastKey.second.size()+2; - size_t len = lastKey.first.size() - off; - - if(lastKey.first.compare(off, len, "equip stop") == 0) mUpperBodyState = UpperCharState_WeapEquiped; - if(lastKey.first.compare(off, len, "unequip stop") == 0) mUpperBodyState = UpperCharState_Nothing; - if(lastKey.first.compare(off, len, "chop large follow stop") == 0){ mUpperBodyState = UpperCharState_WeapEquiped;std::cout << "FINISHED";} - } - it = cls.getCreatureStats(mPtr).getLastAnimKey().erase(it); - }*/ - MWBase::World *world = MWBase::Environment::get().getWorld(); bool onground = world->isOnGround(mPtr); @@ -266,12 +314,12 @@ void CharacterController::update(float duration, Movement &movement) bool sneak = cls.getStance(mPtr, MWWorld::Class::Sneak); Ogre::Vector3 vec = cls.getMovementVector(mPtr); Ogre::Vector3 rot = cls.getRotationVector(mPtr); - speed = cls.getSpeed(mPtr); + mMovementSpeed = cls.getSpeed(mPtr); // advance athletics - if (vec.squaredLength() > 0 && mPtr.getRefData().getHandle() == "player") + if(vec.squaredLength() > 0 && mPtr.getRefData().getHandle() == "player") { - if (inwater) + if(inwater) { mSecondsOfSwimming += duration; while(mSecondsOfSwimming > 1) @@ -280,7 +328,7 @@ void CharacterController::update(float duration, Movement &movement) mSecondsOfSwimming -= 1; } } - else if (isrunning) + else if(isrunning) { mSecondsOfRunning += duration; while(mSecondsOfRunning > 1) @@ -313,54 +361,60 @@ void CharacterController::update(float duration, Movement &movement) //decrease fatigue by fFatigueJumpBase + (1 - normalizedEncumbrance) * fFatigueJumpMult; } - if(std::abs(vec.x/2.0f) > std::abs(vec.y) && speed > 0.0f) + vec.x *= mMovementSpeed; + vec.y *= mMovementSpeed; + + CharacterState movestate = CharState_None; + CharacterState idlestate = CharState_SpecialIdle; + bool forcestateupdate = false; + + if(std::abs(vec.x/2.0f) > std::abs(vec.y)) { if(vec.x > 0.0f) - setState(inwater ? (isrunning ? CharState_SwimRunRight : CharState_SwimWalkRight) - : (sneak ? CharState_SneakRight : (isrunning ? CharState_RunRight : CharState_WalkRight))); + movestate = (inwater ? (isrunning ? CharState_SwimRunRight : CharState_SwimWalkRight) + : (sneak ? CharState_SneakRight + : (isrunning ? CharState_RunRight : CharState_WalkRight))); else if(vec.x < 0.0f) - setState(inwater ? (isrunning ? CharState_SwimRunLeft : CharState_SwimWalkLeft) - : (sneak ? CharState_SneakLeft : (isrunning ? CharState_RunLeft : CharState_WalkLeft))); - - vec.x *= speed; - vec.y *= speed; + movestate = (inwater ? (isrunning ? CharState_SwimRunLeft : CharState_SwimWalkLeft) + : (sneak ? CharState_SneakLeft + : (isrunning ? CharState_RunLeft : CharState_WalkLeft))); } - else if(vec.y != 0.0f && speed > 0.0f) + else if(vec.y != 0.0f) { if(vec.y > 0.0f) - setState(inwater ? (isrunning ? CharState_SwimRunForward : CharState_SwimWalkForward) - : (sneak ? CharState_SneakForward : (isrunning ? CharState_RunForward : CharState_WalkForward))); + movestate = (inwater ? (isrunning ? CharState_SwimRunForward : CharState_SwimWalkForward) + : (sneak ? CharState_SneakForward + : (isrunning ? CharState_RunForward : CharState_WalkForward))); else if(vec.y < 0.0f) - setState(inwater ? (isrunning ? CharState_SwimRunBack : CharState_SwimWalkBack) - : (sneak ? CharState_SneakBack : (isrunning ? CharState_RunBack : CharState_WalkBack))); - - vec.x *= speed; - vec.y *= speed; + movestate = (inwater ? (isrunning ? CharState_SwimRunBack : CharState_SwimWalkBack) + : (sneak ? CharState_SneakBack + : (isrunning ? CharState_RunBack : CharState_WalkBack))); } else if(rot.z != 0.0f && !inwater && !sneak) { if(rot.z > 0.0f) - setState(CharState_TurnRight); + movestate = CharState_TurnRight; else if(rot.z < 0.0f) - setState(CharState_TurnLeft); + movestate = CharState_TurnLeft; } - else if(mAnimQueue.size() > 0) - { - if(mAnimQueue.size() > 1) - { - if(mAnimation->isPlaying(mAnimQueue.front().first) == false) - { - mAnimation->disable(mAnimQueue.front().first); - mAnimQueue.pop_front(); - mAnimation->play(mAnimQueue.front().first, Priority_Default, - MWRender::Animation::Group_All, true, - "start", "stop", 0.0f, mAnimQueue.front().second); - } + if(movestate != CharState_None) + clearAnimQueue(); + + if(mAnimQueue.size() == 0) + idlestate = (inwater ? CharState_IdleSwim : (sneak ? CharState_IdleSneak : CharState_Idle)); + else if(mAnimQueue.size() > 1) + { + if(mAnimation->isPlaying(mAnimQueue.front().first) == false) + { + mAnimation->disable(mAnimQueue.front().first); + mAnimQueue.pop_front(); + + mAnimation->play(mAnimQueue.front().first, Priority_Default, + MWRender::Animation::Group_All, false, + 1.0f, "start", "stop", 0.0f, mAnimQueue.front().second); } } - else - setState((inwater ? CharState_IdleSwim : (sneak ? CharState_IdleSneak : CharState_Idle))); vec *= duration; movement.mPosition[0] += vec.x; @@ -431,20 +485,22 @@ void CharacterController::update(float duration, Movement &movement) if(mUpdateWeapon) { + forcestateupdate = (mWeaponType != weaptype); mWeaponType = weaptype; - forceStateUpdate(); mUpdateWeapon = false; } if(weaptype != mWeaponType) { + forcestateupdate = true; + std::string weapgroup; if(weaptype == WeapType_None) { getWeaponGroup(mWeaponType, weapgroup); mAnimation->play(weapgroup, Priority_Weapon, MWRender::Animation::Group_UpperBody, true, - "unequip start", "unequip stop", 0.0f, 0); + 1.0f, "unequip start", "unequip stop", 0.0f, 0); mUpperBodyState = UpperCharState_UnEquipingWeap; } else @@ -453,12 +509,11 @@ void CharacterController::update(float duration, Movement &movement) mAnimation->showWeapons(false); mAnimation->play(weapgroup, Priority_Weapon, MWRender::Animation::Group_UpperBody, true, - "equip start", "equip stop", 0.0f, 0); + 1.0f, "equip start", "equip stop", 0.0f, 0); mUpperBodyState = UpperCharState_EquipingWeap; } mWeaponType = weaptype; - forceStateUpdate(); if(weapon != inv.end()) { @@ -482,7 +537,7 @@ void CharacterController::update(float duration, Movement &movement) getWeaponGroup(mWeaponType, weapgroup); mAnimation->play(weapgroup, Priority_Weapon, MWRender::Animation::Group_UpperBody, false, - "chop start", "chop min attack", 0.0f, 0); + 1.0f,"chop start", "chop min attack", 0.0f, 0); mUpperBodyState = UpperCharState_ChopStartToMinAttack; } } @@ -501,13 +556,13 @@ void CharacterController::update(float duration, Movement &movement) mAnimation->disable(weapgroup); mAnimation->play(weapgroup, Priority_Weapon, MWRender::Animation::Group_UpperBody, false, - "chop max attack", "chop min hit", 1-complete, 0); + 1.0f,"chop max attack", "chop min hit", 1-complete, 0); } else { mAnimation->play(weapgroup, Priority_Weapon, MWRender::Animation::Group_UpperBody, false, - "chop max attack", "chop min hit", 0, 0); + 1.0f,"chop max attack", "chop min hit", 0, 0); } mUpperBodyState = UpperCharState_ChopMaxAttackToMinHit; } @@ -560,24 +615,24 @@ void CharacterController::update(float duration, Movement &movement) if(!mAnimation->isPlaying("torch")) mAnimation->play("torch", Priority_Torch, MWRender::Animation::Group_LeftArm, false, - "start", "stop", 0.0f, (~(size_t)0)); + 1.0f, "start", "stop", 0.0f, (~(size_t)0)); } else if(mAnimation->isPlaying("torch")) mAnimation->disable("torch"); } + + refreshCurrentAnims(idlestate, movestate, forcestateupdate); } - else if (cls.getCreatureStats(mPtr).isDead()) + else if(cls.getCreatureStats(mPtr).isDead()) { MWBase::Environment::get().getWorld()->enableActorCollision(mPtr, false); } if(mAnimation && !mSkipAnim) { - mAnimation->setSpeed(speed); - Ogre::Vector3 moved = mAnimation->runAnimation(duration); // Ensure we're moving in generally the right direction - if (speed > 0.f) + if(mMovementSpeed > 0.f) { if((movement.mPosition[0] < 0.0f && movement.mPosition[0] < moved.x*2.0f) || (movement.mPosition[0] > 0.0f && movement.mPosition[0] > moved.x*2.0f)) @@ -610,9 +665,12 @@ void CharacterController::playGroup(const std::string &groupname, int mode, int clearAnimQueue(); mAnimQueue.push_back(std::make_pair(groupname, count-1)); - mCharState = CharState_SpecialIdle; + mAnimation->disable(mCurrentIdle); + mCurrentIdle.clear(); + + mIdleState = CharState_SpecialIdle; mAnimation->play(groupname, Priority_Default, - MWRender::Animation::Group_All, false, + MWRender::Animation::Group_All, false, 1.0f, ((mode==2) ? "loop start" : "start"), "stop", 0.0f, count-1); } else if(mode == 0) @@ -632,8 +690,7 @@ bool CharacterController::isAnimPlaying(const std::string &groupName) { if(mAnimation == NULL) return false; - else - return mAnimation->isPlaying(groupName); + return mAnimation->isPlaying(groupName); } @@ -645,27 +702,56 @@ void CharacterController::clearAnimQueue() } -void CharacterController::setState(CharacterState state) -{ - if(mCharState == state) - return; - mCharState = state; - - forceStateUpdate(); -} - void CharacterController::forceStateUpdate() { if(!mAnimation) return; clearAnimQueue(); - std::string group; - Priority prio; - bool loops; - getCurrentGroup(group, prio, loops); - mAnimation->play(group, prio, MWRender::Animation::Group_All, false, - "start", "stop", 0.0f, loops ? (~(size_t)0) : 0); + refreshCurrentAnims(mIdleState, mMovementState, true); + if(mDeathState != CharState_None) + { + const StateInfo *state = std::find_if(sStateList, sStateListEnd, FindCharState(mDeathState)); + if(state == sStateListEnd) + throw std::runtime_error("Failed to find character state "+Ogre::StringConverter::toString(mDeathState)); + + mCurrentDeath = state->groupname; + if(!mAnimation->getInfo(mCurrentDeath)) + mAnimation->play(mCurrentDeath, Priority_Death, MWRender::Animation::Group_All, + false, 1.0f, "start", "stop", 0.0f, 0); + } } +void CharacterController::kill() +{ + static const CharacterState deathstates[] = { + CharState_Death1, CharState_Death2, CharState_Death3, CharState_Death4, CharState_Death5 + }; + + if(mDeathState != CharState_None) + return; + + mDeathState = deathstates[(int)(rand()/((double)RAND_MAX+1.0)*5.0)]; + const StateInfo *state = std::find_if(sStateList, sStateListEnd, FindCharState(mDeathState)); + if(state == sStateListEnd) + throw std::runtime_error("Failed to find character state "+Ogre::StringConverter::toString(mDeathState)); + + mCurrentDeath = state->groupname; + if(mAnimation && !mAnimation->getInfo(mCurrentDeath)) + mAnimation->play(mCurrentDeath, Priority_Death, MWRender::Animation::Group_All, + false, 1.0f, "start", "stop", 0.0f, 0); +} + +void CharacterController::resurrect() +{ + if(mDeathState == CharState_None) + return; + + if(mAnimation) + mAnimation->disable(mCurrentDeath); + mCurrentDeath.empty(); + mDeathState = CharState_None; +} + + } diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 72768d7e7..08dc67f7b 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -17,6 +17,7 @@ class Movement; enum Priority { Priority_Default, + Priority_Movement, Priority_Weapon, Priority_Torch, @@ -26,6 +27,8 @@ enum Priority { }; enum CharacterState { + CharState_None, + CharState_SpecialIdle, CharState_Idle, CharState_Idle2, @@ -121,8 +124,18 @@ class CharacterController typedef std::deque > AnimationQueue; AnimationQueue mAnimQueue; - CharacterState mCharState; + CharacterState mIdleState; + std::string mCurrentIdle; + + CharacterState mMovementState; + std::string mCurrentMovement; + float mMovementSpeed; + + CharacterState mDeathState; + std::string mCurrentDeath; + UpperBodyCharacterState mUpperBodyState; + WeaponType mWeaponType; bool mSkipAnim; @@ -133,15 +146,14 @@ class CharacterController float mSecondsOfSwimming; float mSecondsOfRunning; - // Gets an animation group name from the current character state, and whether it should loop. - void getCurrentGroup(std::string &group, Priority &prio, bool &loops) const; + void refreshCurrentAnims(CharacterState idle, CharacterState movement, bool force=false); static void getWeaponGroup(WeaponType weaptype, std::string &group); void clearAnimQueue(); public: - CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim, CharacterState state); + CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim); virtual ~CharacterController(); void updatePtr(const MWWorld::Ptr &ptr); @@ -152,9 +164,10 @@ public: void skipAnim(); bool isAnimPlaying(const std::string &groupName); - void setState(CharacterState state); - CharacterState getState() const - { return mCharState; } + void kill(); + void resurrect(); + bool isDead() const + { return mDeathState != CharState_None; } void forceStateUpdate(); }; diff --git a/apps/openmw/mwmechanics/objects.cpp b/apps/openmw/mwmechanics/objects.cpp index 24d8a8bf7..deebd8b39 100644 --- a/apps/openmw/mwmechanics/objects.cpp +++ b/apps/openmw/mwmechanics/objects.cpp @@ -17,15 +17,17 @@ Objects::Objects() void Objects::addObject(const MWWorld::Ptr& ptr) { MWRender::Animation *anim = MWBase::Environment::get().getWorld()->getAnimation(ptr); - if(anim != NULL) - mObjects.insert(std::make_pair(ptr, CharacterController(ptr, anim, CharState_Idle))); + if(anim) mObjects.insert(std::make_pair(ptr, new CharacterController(ptr, anim))); } void Objects::removeObject(const MWWorld::Ptr& ptr) { PtrControllerMap::iterator iter = mObjects.find(ptr); if(iter != mObjects.end()) + { + delete iter->second; mObjects.erase(iter); + } } void Objects::updateObject(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr) @@ -33,10 +35,10 @@ void Objects::updateObject(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr) PtrControllerMap::iterator iter = mObjects.find(old); if(iter != mObjects.end()) { - CharacterController ctrl = iter->second; + CharacterController *ctrl = iter->second; mObjects.erase(iter); - ctrl.updatePtr(ptr); + ctrl->updatePtr(ptr); mObjects.insert(std::make_pair(ptr, ctrl)); } } @@ -47,7 +49,10 @@ void Objects::dropObjects (const MWWorld::Ptr::CellStore *cellStore) while(iter != mObjects.end()) { if(iter->first.getCell()==cellStore) + { + delete iter->second; mObjects.erase(iter++); + } else ++iter; } @@ -60,7 +65,7 @@ void Objects::update(float duration, bool paused) for(PtrControllerMap::iterator iter(mObjects.begin());iter != mObjects.end();++iter) { Movement movement; - iter->second.update(duration, movement); + iter->second->update(duration, movement); } } } @@ -69,13 +74,13 @@ void Objects::playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& gro { PtrControllerMap::iterator iter = mObjects.find(ptr); if(iter != mObjects.end()) - iter->second.playGroup(groupName, mode, number); + iter->second->playGroup(groupName, mode, number); } void Objects::skipAnimation(const MWWorld::Ptr& ptr) { PtrControllerMap::iterator iter = mObjects.find(ptr); if(iter != mObjects.end()) - iter->second.skipAnim(); + iter->second->skipAnim(); } } diff --git a/apps/openmw/mwmechanics/objects.hpp b/apps/openmw/mwmechanics/objects.hpp index 7b1185a29..5cdcdaa0a 100644 --- a/apps/openmw/mwmechanics/objects.hpp +++ b/apps/openmw/mwmechanics/objects.hpp @@ -16,7 +16,7 @@ namespace MWMechanics { class Objects { - typedef std::map PtrControllerMap; + typedef std::map PtrControllerMap; PtrControllerMap mObjects; public: diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 853ffc375..147e7c90e 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -45,14 +45,13 @@ void Animation::destroyObjectList(Ogre::SceneManager *sceneMgr, NifOgre::ObjectL Animation::Animation(const MWWorld::Ptr &ptr) : mPtr(ptr) + , mCamera(NULL) , mInsert(NULL) , mSkelBase(NULL) , mAccumRoot(NULL) , mNonAccumRoot(NULL) , mNonAccumCtrl(NULL) , mAccumulate(0.0f) - , mAnimVelocity(0.0f) - , mAnimSpeedMult(1.0f) { for(size_t i = 0;i < sNumGroups;i++) mAnimationValuePtr[i].bind(OGRE_NEW AnimationValue(this)); @@ -72,8 +71,9 @@ Animation::~Animation() void Animation::setObjectRoot(Ogre::SceneNode *node, const std::string &model, bool baseonly) { - OgreAssert(!mInsert, "Object already has a root!"); - mInsert = node->createChildSceneNode(); + OgreAssert(mAnimSources.size() == 0, "Setting object root while animation sources are set!"); + if(!mInsert) + mInsert = node->createChildSceneNode(); std::string mdlname = Misc::StringUtils::lowerCase(model); std::string::size_type p = mdlname.rfind('\\'); @@ -89,6 +89,9 @@ void Animation::setObjectRoot(Ogre::SceneNode *node, const std::string &model, b Misc::StringUtils::toLower(mdlname); } + mSkelBase = NULL; + destroyObjectList(mInsert->getCreator(), mObjectRoot); + mObjectRoot = (!baseonly ? NifOgre::Loader::createObjects(mInsert, mdlname) : NifOgre::Loader::createObjectBase(mInsert, mdlname)); if(mObjectRoot.mSkelBase) @@ -110,7 +113,23 @@ void Animation::setObjectRoot(Ogre::SceneNode *node, const std::string &model, b Ogre::Skeleton::BoneIterator boneiter = skelinst->getBoneIterator(); while(boneiter.hasMoreElements()) boneiter.getNext()->setManuallyControlled(true); + + // Reattach any objects that have been attached to this one + ObjectAttachMap::iterator iter = mAttachedObjects.begin(); + while(iter != mAttachedObjects.end()) + { + if(!skelinst->hasBone(iter->second)) + mAttachedObjects.erase(iter++); + else + { + mSkelBase->attachObjectToBone(iter->second, iter->first); + iter++; + } + } } + else + mAttachedObjects.clear(); + for(size_t i = 0;i < mObjectRoot.mControllers.size();i++) { if(mObjectRoot.mControllers[i].getSource().isNull()) @@ -224,7 +243,6 @@ void Animation::clearAnimSources() mAnimationValuePtr[i]->setAnimName(std::string()); mNonAccumCtrl = NULL; - mAnimVelocity = 0.0f; mAccumRoot = NULL; mNonAccumRoot = NULL; @@ -277,13 +295,6 @@ void Animation::setAccumulation(const Ogre::Vector3 &accum) mAccumulate = accum; } -void Animation::setSpeed(float speed) -{ - mAnimSpeedMult = 1.0f; - if(speed > 0.0f && mAnimVelocity > 1.0f) - mAnimSpeedMult = speed / mAnimVelocity; -} - void Animation::updatePtr(const MWWorld::Ptr &ptr) { @@ -323,6 +334,61 @@ float Animation::calcAnimVelocity(const NifOgre::TextKeyMap &keys, NifOgre::Node return 0.0f; } +float Animation::getVelocity(const std::string &groupname) const +{ + /* Look in reverse; last-inserted source has priority. */ + AnimSourceList::const_reverse_iterator animsrc(mAnimSources.rbegin()); + for(;animsrc != mAnimSources.rend();animsrc++) + { + const NifOgre::TextKeyMap &keys = (*animsrc)->mTextKeys; + if(findGroupStart(keys, groupname) != keys.end()) + break; + } + if(animsrc == mAnimSources.rend()) + return 0.0f; + + float velocity = 0.0f; + const NifOgre::TextKeyMap &keys = (*animsrc)->mTextKeys; + const std::vector >&ctrls = (*animsrc)->mControllers[0]; + for(size_t i = 0;i < ctrls.size();i++) + { + NifOgre::NodeTargetValue *dstval; + dstval = static_cast*>(ctrls[i].getDestination().getPointer()); + if(dstval->getNode() == mNonAccumRoot) + { + velocity = calcAnimVelocity(keys, dstval, mAccumulate, groupname); + break; + } + } + + // If there's no velocity, keep looking + if(!(velocity > 1.0f)) + { + AnimSourceList::const_reverse_iterator animiter = mAnimSources.rbegin(); + while(*animiter != *animsrc) + ++animiter; + + while(!(velocity > 1.0f) && ++animiter != mAnimSources.rend()) + { + const NifOgre::TextKeyMap &keys = (*animiter)->mTextKeys; + const std::vector >&ctrls = (*animiter)->mControllers[0]; + for(size_t i = 0;i < ctrls.size();i++) + { + NifOgre::NodeTargetValue *dstval; + dstval = static_cast*>(ctrls[i].getDestination().getPointer()); + if(dstval->getNode() == mNonAccumRoot) + { + velocity = calcAnimVelocity(keys, dstval, mAccumulate, groupname); + break; + } + } + } + } + + return velocity; +} + + static void updateBoneTree(const Ogre::SkeletonInstance *skelsrc, Ogre::Bone *bone) { if(skelsrc->hasBone(bone->getName())) @@ -496,7 +562,7 @@ bool Animation::handleTextKey(AnimState &state, const std::string &groupname, co } -void Animation::play(const std::string &groupname, int priority, int groups, bool autodisable, const std::string &start, const std::string &stop, float startpoint, size_t loops) +void Animation::play(const std::string &groupname, int priority, int groups, bool autodisable, float speedmult, const std::string &start, const std::string &stop, float startpoint, size_t loops) { if(!mSkelBase) return; @@ -534,6 +600,7 @@ void Animation::play(const std::string &groupname, int priority, int groups, boo if(reset(state, (*iter)->mTextKeys, groupname, start, stop, startpoint)) { state.mSource = *iter; + state.mSpeedMult = speedmult; state.mLoopCount = loops; state.mPlaying = true; state.mPriority = priority; @@ -577,9 +644,7 @@ void Animation::resetActiveGroups() mAnimationValuePtr[grp]->setAnimName((active == mStates.end()) ? std::string() : active->first); } - mNonAccumCtrl = NULL; - mAnimVelocity = 0.0f; if(!mNonAccumRoot || mAccumulate == Ogre::Vector3(0.0f)) return; @@ -597,44 +662,20 @@ void Animation::resetActiveGroups() dstval = static_cast*>(ctrls[i].getDestination().getPointer()); if(dstval->getNode() == mNonAccumRoot) { - mAnimVelocity = calcAnimVelocity(keys, dstval, mAccumulate, state->first); mNonAccumCtrl = dstval; break; } } - - // If there's no velocity, keep looking - if(!(mAnimVelocity > 1.0f)) - { - AnimSourceList::const_reverse_iterator animiter = mAnimSources.rbegin(); - while(*animiter != animsrc) - ++animiter; - - while(!(mAnimVelocity > 1.0f) && ++animiter != mAnimSources.rend()) - { - const NifOgre::TextKeyMap &keys = (*animiter)->mTextKeys; - const std::vector >&ctrls = (*animiter)->mControllers[0]; - for(size_t i = 0;i < ctrls.size();i++) - { - NifOgre::NodeTargetValue *dstval; - dstval = static_cast*>(ctrls[i].getDestination().getPointer()); - if(dstval->getNode() == mNonAccumRoot) - { - mAnimVelocity = calcAnimVelocity(keys, dstval, mAccumulate, state->first); - break; - } - } - } - } } -bool Animation::getInfo(const std::string &groupname, float *complete, std::string *start, std::string *stop) const +bool Animation::getInfo(const std::string &groupname, float *complete, float *speedmult, std::string *start, std::string *stop) const { AnimStateMap::const_iterator iter = mStates.find(groupname); if(iter == mStates.end()) { if(complete) *complete = 0.0f; + if(speedmult) *speedmult = 0.0f; if(start) *start = ""; if(stop) *stop = ""; return false; @@ -642,6 +683,7 @@ bool Animation::getInfo(const std::string &groupname, float *complete, std::stri if(complete) *complete = (iter->second.mTime - iter->second.mStartKey->first) / (iter->second.mStopKey->first - iter->second.mStartKey->first); + if(speedmult) *speedmult = iter->second.mSpeedMult; if(start) *start = iter->second.mStartKey->second.substr(groupname.size()+2); if(stop) *stop = iter->second.mStopKey->second.substr(groupname.size()+2); return true; @@ -661,12 +703,11 @@ Ogre::Vector3 Animation::runAnimation(float duration) { Ogre::Vector3 movement(0.0f); - duration *= mAnimSpeedMult; AnimStateMap::iterator stateiter = mStates.begin(); while(stateiter != mStates.end()) { AnimState &state = stateiter->second; - float timepassed = duration; + float timepassed = duration * state.mSpeedMult; while(state.mPlaying) { float targetTime = state.mTime + timepassed; @@ -736,4 +777,24 @@ bool Animation::isPriorityActive(int priority) const return false; } +Ogre::TagPoint *Animation::attachObjectToBone(const Ogre::String &bonename, Ogre::MovableObject *obj) +{ + Ogre::TagPoint *tag = NULL; + Ogre::SkeletonInstance *skel = (mSkelBase ? mSkelBase->getSkeleton() : NULL); + if(skel && skel->hasBone(bonename)) + { + tag = mSkelBase->attachObjectToBone(bonename, obj); + mAttachedObjects[obj] = bonename; + } + return tag; +} + +void Animation::detachObjectFromBone(Ogre::MovableObject *obj) +{ + ObjectAttachMap::iterator iter = mAttachedObjects.find(obj); + if(iter != mAttachedObjects.end()) + mAttachedObjects.erase(iter); + mSkelBase->detachObjectFromBone(obj); +} + } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 31be0fb2a..bc7fd3d22 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -11,6 +11,7 @@ namespace MWRender { +class Camera; class Animation { @@ -65,6 +66,7 @@ protected: NifOgre::TextKeyMap::const_iterator mNextKey; float mTime; + float mSpeedMult; bool mPlaying; size_t mLoopCount; @@ -73,13 +75,16 @@ protected: int mGroups; bool mAutoDisable; - AnimState() : mTime(0.0f), mPlaying(false), mLoopCount(0), + AnimState() : mTime(0.0f), mSpeedMult(1.0f), mPlaying(false), mLoopCount(0), mPriority(0), mGroups(0), mAutoDisable(true) { } }; typedef std::map AnimStateMap; + typedef std::map ObjectAttachMap; + MWWorld::Ptr mPtr; + Camera *mCamera; Ogre::SceneNode *mInsert; Ogre::Entity *mSkelBase; @@ -94,8 +99,7 @@ protected: Ogre::SharedPtr mAnimationValuePtr[sNumGroups]; - float mAnimVelocity; - float mAnimSpeedMult; + ObjectAttachMap mAttachedObjects; /* Sets the appropriate animations on the bone groups based on priority. */ @@ -131,7 +135,18 @@ protected: bool handleTextKey(AnimState &state, const std::string &groupname, const NifOgre::TextKeyMap::const_iterator &key); + /* Sets the root model of the object. If 'baseonly' is true, then any meshes or particle + * systems in the model are ignored (useful for NPCs, where only the skeleton is needed for + * the root). + * + * Note that you must make sure all animation sources are cleared before reseting the object + * root. All nodes previously retrieved with getNode will also become invalidated. + */ void setObjectRoot(Ogre::SceneNode *node, const std::string &model, bool baseonly); + + /* Adds the keyframe controllers in the specified model as a new animation source. Note that + * the filename portion of the provided model name will be prepended with 'x', and the .nif + * extension will be replaced with .kf. */ void addAnimSource(const std::string &model); static void destroyObjectList(Ogre::SceneManager *sceneMgr, NifOgre::ObjectList &objects); @@ -156,8 +171,6 @@ public: // should be on the scale of 0 to 1. void setAccumulation(const Ogre::Vector3 &accum); - void setSpeed(float speed); - /** Plays an animation. * \param groupname Name of the animation group to play. * \param priority Priority of the animation. The animation will play on @@ -166,6 +179,7 @@ public: * \param groups Bone groups to play the animation on. * \param autodisable Automatically disable the animation when it stops * playing. + * \param speedmult Speed multiplier for the animation. * \param start Key marker from which to start. * \param stop Key marker to stop at. * \param startpoint How far in between the two markers to start. 0 starts @@ -175,7 +189,7 @@ public: * otherwise it will use "start" and "stop". */ void play(const std::string &groupname, int priority, int groups, bool autodisable, - const std::string &start, const std::string &stop, + float speedmult, const std::string &start, const std::string &stop, float startpoint, size_t loops); /** Returns true if the named animation group is playing. */ @@ -184,22 +198,35 @@ public: /** Gets info about the given animation group. * \param groupname Animation group to check. * \param complete Stores completion amount (0 = at start key, 0.5 = half way between start and stop keys), etc. + * \param speedmult Stores the animation speed multiplier * \param start Stores the start key * \param stop Stores the stop key * \return True if the animation is active, false otherwise. */ - bool getInfo(const std::string &groupname, float *complete=NULL, std::string *start=NULL, std::string *stop=NULL) const; + bool getInfo(const std::string &groupname, float *complete=NULL, float *speedmult=NULL, std::string *start=NULL, std::string *stop=NULL) const; /** Disables the specified animation group; * \param groupname Animation group to disable. */ void disable(const std::string &groupname); + /** Retrieves the velocity (in units per second) that the animation will move. */ + float getVelocity(const std::string &groupname) const; + virtual Ogre::Vector3 runAnimation(float duration); virtual void showWeapons(bool showWeapon); + void setCamera(Camera *cam) + { mCamera = cam; } + Ogre::Node *getNode(const std::string &name); + + // Attaches the given object to a bone on this object's base skeleton. If the bone doesn't + // exist, the object isn't attached and NULL is returned. The returned TagPoint is only + // valid until the next setObjectRoot call. + Ogre::TagPoint *attachObjectToBone(const Ogre::String &bonename, Ogre::MovableObject *obj); + void detachObjectFromBone(Ogre::MovableObject *obj); }; } diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index e71e694f9..b763d84ac 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" @@ -57,10 +58,10 @@ namespace MWRender Ogre::Quaternion xr(Ogre::Radian(getPitch() + Ogre::Math::HALF_PI), Ogre::Vector3::UNIT_X); if (!mVanity.enabled && !mPreviewMode) { - mCameraNode->setOrientation(xr); + mCamera->getParentNode()->setOrientation(xr); } else { Ogre::Quaternion zr(Ogre::Radian(getYaw()), Ogre::Vector3::NEGATIVE_UNIT_Z); - mCameraNode->setOrientation(zr * xr); + mCamera->getParentNode()->setOrientation(zr * xr); } } @@ -81,6 +82,7 @@ namespace MWRender mCameraNode->getCreator()->destroySceneNode(mCameraNode); } mCameraNode = node; + mCamera->detachFromParent(); mCameraNode->attachObject(mCamera); } @@ -112,14 +114,12 @@ namespace MWRender void Camera::toggleViewMode() { mFirstPersonView = !mFirstPersonView; - mAnimation->setViewMode(isFirstPerson() ? NpcAnimation::VM_FirstPerson : - NpcAnimation::VM_Normal); + processViewChange(); + if (mFirstPersonView) { mCamera->setPosition(0.f, 0.f, 0.f); - setLowHeight(false); } else { mCamera->setPosition(0.f, 0.f, mCameraDistance); - setLowHeight(true); } } @@ -139,21 +139,16 @@ namespace MWRender return true; mVanity.enabled = enable; - mAnimation->setViewMode(isFirstPerson() ? NpcAnimation::VM_FirstPerson : - NpcAnimation::VM_Normal); + processViewChange(); float offset = mPreviewCam.offset; Ogre::Vector3 rot(0.f, 0.f, 0.f); if (mVanity.enabled) { rot.x = Ogre::Degree(-30.f).valueRadians(); mMainCam.offset = mCamera->getPosition().z; - - setLowHeight(true); } else { rot.x = getPitch(); offset = mMainCam.offset; - - setLowHeight(!mFirstPersonView); } rot.z = getYaw(); @@ -169,20 +164,15 @@ namespace MWRender return; mPreviewMode = enable; - mAnimation->setViewMode(isFirstPerson() ? NpcAnimation::VM_FirstPerson : - NpcAnimation::VM_Normal); + processViewChange(); float offset = mCamera->getPosition().z; if (mPreviewMode) { mMainCam.offset = offset; offset = mPreviewCam.offset; - - setLowHeight(true); } else { mPreviewCam.offset = offset; offset = mMainCam.offset; - - setLowHeight(!mFirstPersonView); } mCamera->setPosition(0.f, 0.f, offset); @@ -283,26 +273,48 @@ namespace MWRender // If we're switching to a new NpcAnimation, ensure the old one is // using a normal view mode if(mAnimation && mAnimation != anim) + { mAnimation->setViewMode(NpcAnimation::VM_Normal); + mAnimation->setCamera(NULL); + mAnimation->detachObjectFromBone(mCamera); + } mAnimation = anim; - mAnimation->setViewMode(isFirstPerson() ? NpcAnimation::VM_FirstPerson : - NpcAnimation::VM_Normal); + mAnimation->setCamera(this); + + processViewChange(); } - void Camera::setHeight(float height) + void Camera::processViewChange() { - mHeight = height; - mCameraNode->setPosition(0.f, 0.f, mHeight); + mAnimation->detachObjectFromBone(mCamera); + mCamera->detachFromParent(); + + if(isFirstPerson()) + { + mAnimation->setViewMode(NpcAnimation::VM_FirstPerson); + Ogre::TagPoint *tag = mAnimation->attachObjectToBone("Head", mCamera); + tag->setInheritOrientation(false); + } + else + { + mAnimation->setViewMode(NpcAnimation::VM_Normal); + mCameraNode->attachObject(mCamera); + } } float Camera::getHeight() { - return mHeight * mTrackingPtr.getRefData().getBaseNode()->getScale().z; + if(mCamera->isParentTagPoint()) + { + Ogre::TagPoint *tag = static_cast(mCamera->getParentNode()); + return tag->_getFullLocalTransform().getTrans().z; + } + return mCamera->getParentNode()->getPosition().z; } bool Camera::getPosition(Ogre::Vector3 &player, Ogre::Vector3 &camera) { - mCamera->getParentSceneNode ()->needUpdate(true); + mCamera->getParentSceneNode()->needUpdate(true); camera = mCamera->getRealPosition(); player = mTrackingPtr.getRefData().getBaseNode()->getPosition(); @@ -325,15 +337,6 @@ namespace MWRender mFreeLook = enable; } - void Camera::setLowHeight(bool low) - { - if (low) { - mCameraNode->setPosition(0.f, 0.f, mHeight * 0.85); - } else { - mCameraNode->setPosition(0.f, 0.f, mHeight); - } - } - bool Camera::isVanityOrPreviewModeEnabled() { return mPreviewMode || mVanity.enabled; diff --git a/apps/openmw/mwrender/camera.hpp b/apps/openmw/mwrender/camera.hpp index ad5e35f93..3418efcc9 100644 --- a/apps/openmw/mwrender/camera.hpp +++ b/apps/openmw/mwrender/camera.hpp @@ -46,8 +46,6 @@ namespace MWRender /// Updates sound manager listener data void updateListener(); - void setLowHeight(bool low = true); - public: Camera(Ogre::Camera *camera); ~Camera(); @@ -80,6 +78,8 @@ namespace MWRender bool isFirstPerson() const { return !(mVanity.enabled || mPreviewMode || !mFirstPersonView); } + void processViewChange(); + void update(float duration); /// Set camera distance for current mode. Don't work on 1st person view. @@ -93,7 +93,6 @@ namespace MWRender void setAnimation(NpcAnimation *anim); - void setHeight(float height); float getHeight(); /// Stores player and camera world positions in passed arguments diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index e9ecedc55..88d870445 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -44,9 +44,14 @@ namespace MWRender { mSceneMgr = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC); - /// \todo Read the fallback values from INIImporter (Inventory:Directional*) + // This is a dummy light to turn off shadows without having to use a separate set of shaders Ogre::Light* l = mSceneMgr->createLight(); l->setType (Ogre::Light::LT_DIRECTIONAL); + l->setDiffuseColour (Ogre::ColourValue(0,0,0)); + + /// \todo Read the fallback values from INIImporter (Inventory:Directional*) + l = mSceneMgr->createLight(); + l->setType (Ogre::Light::LT_DIRECTIONAL); l->setDirection (Ogre::Vector3(0.3, -0.7, 0.3)); l->setDiffuseColour (Ogre::ColourValue(1,1,1)); @@ -172,7 +177,7 @@ namespace MWRender if(groupname != mCurrentAnimGroup) { mCurrentAnimGroup = groupname; - mAnimation->play(mCurrentAnimGroup, 1, Animation::Group_All, false, "start", "stop", 0.0f, 0); + mAnimation->play(mCurrentAnimGroup, 1, Animation::Group_All, false, 1.0f, "start", "stop", 0.0f, 0); } MWWorld::ContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); @@ -180,7 +185,7 @@ namespace MWRender { if(!mAnimation->getInfo("torch")) mAnimation->play("torch", 2, MWRender::Animation::Group_LeftArm, false, - "start", "stop", 0.0f, (~(size_t)0)); + 1.0f, "start", "stop", 0.0f, ~0ul); } else if(mAnimation->getInfo("torch")) mAnimation->disable("torch"); @@ -210,7 +215,7 @@ namespace MWRender mAnimation->showWeapons(true); mCurrentAnimGroup = "inventoryhandtohand"; - mAnimation->play(mCurrentAnimGroup, 1, Animation::Group_All, false, "start", "stop", 0.0f, 0); + mAnimation->play(mCurrentAnimGroup, 1, Animation::Group_All, false, 1.0f, "start", "stop", 0.0f, 0); } // -------------------------------------------------------------------------------------------------- @@ -247,7 +252,7 @@ namespace MWRender void RaceSelectionPreview::onSetup () { - mAnimation->play("idle", 1, Animation::Group_All, false, "start", "stop", 0.0f, 0); + mAnimation->play("idle", 1, Animation::Group_All, false, 1.0f, "start", "stop", 0.0f, 0); updateCamera(); } diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index b5f2ea031..f2df5ccd5 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -14,6 +14,7 @@ #include "../mwbase/mechanicsmanager.hpp" #include "renderconst.hpp" +#include "camera.hpp" namespace MWRender @@ -98,17 +99,22 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, MWWor Misc::StringUtils::toLower(mBodyPrefix); bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0; - std::string smodel = (!isBeast ? "meshes\\base_anim.nif" : "meshes\\base_animkna.nif"); + std::string smodel = (viewMode != VM_FirstPerson) ? + (!isBeast ? "meshes\\base_anim.nif" : "meshes\\base_animkna.nif") : + (!isBeast ? "meshes\\base_anim.1st.nif" : "meshes\\base_animkna.1st.nif") ; setObjectRoot(node, smodel, true); - addAnimSource(smodel); - if(mBodyPrefix.find("argonian") != std::string::npos) - addAnimSource("meshes\\argonian_swimkna.nif"); - else if(!mNpc->isMale() && !isBeast) - addAnimSource("meshes\\base_anim_female.nif"); - if(mNpc->mModel.length() > 0) - addAnimSource("meshes\\"+mNpc->mModel); - if(mViewMode == VM_FirstPerson) + if(mViewMode != VM_FirstPerson) + { + addAnimSource(smodel); + if(mBodyPrefix.find("argonian") != std::string::npos) + addAnimSource("meshes\\argonian_swimkna.nif"); + else if(!mNpc->isMale() && !isBeast) + addAnimSource("meshes\\base_anim_female.nif"); + if(mNpc->mModel.length() > 0) + addAnimSource("meshes\\"+mNpc->mModel); + } + else { /* A bit counter-intuitive, but unlike third-person anims, it seems * beast races get both base_anim.1st.nif and base_animkna.1st.nif. @@ -128,20 +134,28 @@ void NpcAnimation::setViewMode(NpcAnimation::ViewMode viewMode) assert(viewMode != VM_HeadOnly); mViewMode = viewMode; + clearAnimSources(); + const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Race *race = store.get().find(mNpc->mRace); - bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0; - std::string smodel = (!isBeast ? "meshes\\base_anim.nif" : "meshes\\base_animkna.nif"); - clearAnimSources(); - addAnimSource(smodel); - if(mBodyPrefix.find("argonian") != std::string::npos) - addAnimSource("meshes\\argonian_swimkna.nif"); - else if(!mNpc->isMale() && !isBeast) - addAnimSource("meshes\\base_anim_female.nif"); - if(mNpc->mModel.length() > 0) - addAnimSource("meshes\\"+mNpc->mModel); - if(mViewMode == VM_FirstPerson) + bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0; + std::string smodel = (viewMode != VM_FirstPerson) ? + (!isBeast ? "meshes\\base_anim.nif" : "meshes\\base_animkna.nif") : + (!isBeast ? "meshes\\base_anim.1st.nif" : "meshes\\base_animkna.1st.nif") ; + setObjectRoot(mInsert->getParentSceneNode(), smodel, true); + + if(mViewMode != VM_FirstPerson) + { + addAnimSource(smodel); + if(mBodyPrefix.find("argonian") != std::string::npos) + addAnimSource("meshes\\argonian_swimkna.nif"); + else if(!mNpc->isMale() && !isBeast) + addAnimSource("meshes\\base_anim_female.nif"); + if(mNpc->mModel.length() > 0) + addAnimSource("meshes\\"+mNpc->mModel); + } + else { /* A bit counter-intuitive, but unlike third-person anims, it seems * beast races get both base_anim.1st.nif and base_animkna.1st.nif. @@ -197,19 +211,11 @@ void NpcAnimation::updateParts(bool forceupdate) if(!forceupdate) return; - /* FIXME: Remove this once we figure out how to show what in first-person */ - if(mViewMode == VM_FirstPerson) - { - for(size_t i = 0;i < slotlistsize;i++) - this->*slotlist[i].mPart = inv.getSlot(slotlist[i].mSlot); - return; - } - for(size_t i = 0;i < slotlistsize && mViewMode != VM_HeadOnly;i++) { - MWWorld::ContainerStoreIterator iter = inv.getSlot(slotlist[i].mSlot); + MWWorld::ContainerStoreIterator store = inv.getSlot(slotlist[i].mSlot); - this->*slotlist[i].mPart = iter; + this->*slotlist[i].mPart = store; removePartGroup(slotlist[i].mSlot); if(this->*slotlist[i].mPart == inv.end()) @@ -219,7 +225,6 @@ void NpcAnimation::updateParts(bool forceupdate) removeIndividualPart(ESM::PRT_Hair); int prio = 1; - MWWorld::ContainerStoreIterator &store = this->*slotlist[i].mPart; if(store->getTypeName() == typeid(ESM::Clothing).name()) { prio = ((slotlist[i].mBasePriority+1)<<1) + 0; @@ -279,32 +284,34 @@ void NpcAnimation::updateParts(bool forceupdate) std::pair thisCombination = std::make_pair(race, flags); if (sRaceMapping.find(thisCombination) == sRaceMapping.end()) { - static std::map bodypartMap; - if(bodypartMap.size() == 0) + typedef std::multimap BodyPartMapType; + static BodyPartMapType sBodyPartMap; + if(sBodyPartMap.size() == 0) { - bodypartMap[ESM::PRT_Neck] = ESM::BodyPart::MP_Neck; - bodypartMap[ESM::PRT_Cuirass] = ESM::BodyPart::MP_Chest; - bodypartMap[ESM::PRT_Groin] = ESM::BodyPart::MP_Groin; - bodypartMap[ESM::PRT_RHand] = ESM::BodyPart::MP_Hand; - bodypartMap[ESM::PRT_LHand] = ESM::BodyPart::MP_Hand; - bodypartMap[ESM::PRT_RWrist] = ESM::BodyPart::MP_Wrist; - bodypartMap[ESM::PRT_LWrist] = ESM::BodyPart::MP_Wrist; - bodypartMap[ESM::PRT_RForearm] = ESM::BodyPart::MP_Forearm; - bodypartMap[ESM::PRT_LForearm] = ESM::BodyPart::MP_Forearm; - bodypartMap[ESM::PRT_RUpperarm] = ESM::BodyPart::MP_Upperarm; - bodypartMap[ESM::PRT_LUpperarm] = ESM::BodyPart::MP_Upperarm; - bodypartMap[ESM::PRT_RFoot] = ESM::BodyPart::MP_Foot; - bodypartMap[ESM::PRT_LFoot] = ESM::BodyPart::MP_Foot; - bodypartMap[ESM::PRT_RAnkle] = ESM::BodyPart::MP_Ankle; - bodypartMap[ESM::PRT_LAnkle] = ESM::BodyPart::MP_Ankle; - bodypartMap[ESM::PRT_RKnee] = ESM::BodyPart::MP_Knee; - bodypartMap[ESM::PRT_LKnee] = ESM::BodyPart::MP_Knee; - bodypartMap[ESM::PRT_RLeg] = ESM::BodyPart::MP_Upperleg; - bodypartMap[ESM::PRT_LLeg] = ESM::BodyPart::MP_Upperleg; - bodypartMap[ESM::PRT_Tail] = ESM::BodyPart::MP_Tail; + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Neck, ESM::PRT_Neck)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Chest, ESM::PRT_Cuirass)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Groin, ESM::PRT_Groin)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Hand, ESM::PRT_RHand)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Hand, ESM::PRT_LHand)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Wrist, ESM::PRT_RWrist)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Wrist, ESM::PRT_LWrist)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Forearm, ESM::PRT_RForearm)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Forearm, ESM::PRT_LForearm)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperarm, ESM::PRT_RUpperarm)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperarm, ESM::PRT_LUpperarm)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Foot, ESM::PRT_RFoot)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Foot, ESM::PRT_LFoot)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Ankle, ESM::PRT_RAnkle)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Ankle, ESM::PRT_LAnkle)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Knee, ESM::PRT_RKnee)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Knee, ESM::PRT_LKnee)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperleg, ESM::PRT_RLeg)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Upperleg, ESM::PRT_LLeg)); + sBodyPartMap.insert(std::make_pair(ESM::BodyPart::MP_Tail, ESM::PRT_Tail)); } - sRaceMapping[thisCombination].resize(ESM::PRT_Count, NULL); + std::vector &parts = sRaceMapping[thisCombination]; + parts.resize(ESM::PRT_Count, NULL); const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const MWWorld::Store &partStore = store.get(); @@ -325,29 +332,55 @@ void NpcAnimation::updateParts(bool forceupdate) && bodypart.mId[bodypart.mId.size()-3] == '1' && bodypart.mId[bodypart.mId.size()-2] == 's' && bodypart.mId[bodypart.mId.size()-1] == 't'; - if (firstPerson != (mViewMode == VM_FirstPerson)) + if(firstPerson != (mViewMode == VM_FirstPerson)) + { + if(mViewMode == VM_FirstPerson && (bodypart.mData.mPart == ESM::BodyPart::MP_Hand || + bodypart.mData.mPart == ESM::BodyPart::MP_Wrist || + bodypart.mData.mPart == ESM::BodyPart::MP_Forearm || + bodypart.mData.mPart == ESM::BodyPart::MP_Upperarm)) + { + /* Allow 3rd person skins as a fallback for the arms if 1st person is missing. */ + BodyPartMapType::const_iterator bIt = sBodyPartMap.lower_bound(BodyPartMapType::key_type(bodypart.mData.mPart)); + while(bIt != sBodyPartMap.end() && bIt->first == bodypart.mData.mPart) + { + if(!parts[bIt->second]) + parts[bIt->second] = &*it; + bIt++; + } + } continue; - for (std::map::iterator bIt = bodypartMap.begin(); bIt != bodypartMap.end(); ++bIt ) - if (bIt->second == bodypart.mData.mPart) - sRaceMapping[thisCombination][bIt->first] = &*it; + } + BodyPartMapType::const_iterator bIt = sBodyPartMap.lower_bound(BodyPartMapType::key_type(bodypart.mData.mPart)); + while(bIt != sBodyPartMap.end() && bIt->first == bodypart.mData.mPart) + { + parts[bIt->second] = &*it; + bIt++; + } } } - for (int part = ESM::PRT_Neck; part < ESM::PRT_Count; ++part) + const std::vector &parts = sRaceMapping[thisCombination]; + for(int part = ESM::PRT_Neck; part < ESM::PRT_Count; ++part) { - const ESM::BodyPart* bodypart = sRaceMapping[thisCombination][part]; - if (mPartPriorities[part] < 1 && bodypart) - addOrReplaceIndividualPart(part, -1,1, "meshes\\"+bodypart->mModel); + if(mPartPriorities[part] < 1) + { + const ESM::BodyPart* bodypart = parts[part]; + if(bodypart) + addOrReplaceIndividualPart(part, -1,1, "meshes\\"+bodypart->mModel); + } } } NifOgre::ObjectList NpcAnimation::insertBoundedPart(const std::string &model, int group, const std::string &bonename) { NifOgre::ObjectList objects = NifOgre::Loader::createObjects(mSkelBase, bonename, mInsert, model); - setRenderProperties(objects, mVisibilityFlags, RQG_Main, RQG_Alpha); + setRenderProperties(objects, (mViewMode == VM_FirstPerson) ? RV_FirstPerson : mVisibilityFlags, RQG_Main, RQG_Alpha); for(size_t i = 0;i < objects.mEntities.size();i++) - objects.mEntities[i]->getUserObjectBindings().setUserAny(Ogre::Any(group)); + { + Ogre::Entity *ent = objects.mEntities[i]; + ent->getUserObjectBindings().setUserAny(Ogre::Any(group)); + } for(size_t i = 0;i < objects.mParticles.size();i++) objects.mParticles[i]->getUserObjectBindings().setUserAny(Ogre::Any(group)); @@ -382,6 +415,13 @@ Ogre::Vector3 NpcAnimation::runAnimation(float timepassed) Ogre::Vector3 ret = Animation::runAnimation(timepassed); Ogre::SkeletonInstance *baseinst = mSkelBase->getSkeleton(); + if(mViewMode == VM_FirstPerson && mCamera) + { + float pitch = mCamera->getPitch(); + Ogre::Node *node = baseinst->getBone("Bip01 Neck"); + node->pitch(Ogre::Radian(pitch*0.75f), Ogre::Node::TS_WORLD); + } + for(size_t i = 0;i < sPartListSize;i++) { Ogre::Entity *ent = mObjectParts[i].mSkelBase; @@ -458,9 +498,31 @@ void NpcAnimation::addPartGroup(int group, int priority, const std::vectorisMale() && !part->mFemale.empty()) + { bodypart = partStore.search(part->mFemale+ext); + if(!bodypart && mViewMode == VM_FirstPerson) + { + bodypart = partStore.search(part->mFemale); + if(bodypart && !(bodypart->mData.mPart == ESM::BodyPart::MP_Hand || + bodypart->mData.mPart == ESM::BodyPart::MP_Wrist || + bodypart->mData.mPart == ESM::BodyPart::MP_Forearm || + bodypart->mData.mPart == ESM::BodyPart::MP_Upperarm)) + bodypart = NULL; + } + } if(!bodypart && !part->mMale.empty()) + { bodypart = partStore.search(part->mMale+ext); + if(!bodypart && mViewMode == VM_FirstPerson) + { + bodypart = partStore.search(part->mMale); + if(bodypart && !(bodypart->mData.mPart == ESM::BodyPart::MP_Hand || + bodypart->mData.mPart == ESM::BodyPart::MP_Wrist || + bodypart->mData.mPart == ESM::BodyPart::MP_Forearm || + bodypart->mData.mPart == ESM::BodyPart::MP_Upperarm)) + bodypart = NULL; + } + } if(bodypart) addOrReplaceIndividualPart(part->mPart, group, priority, "meshes\\"+bodypart->mModel); @@ -472,8 +534,7 @@ void NpcAnimation::addPartGroup(int group, int priority, const std::vectorscaleObject(ptr, 1.f); } -void RenderingManager::getCameraData(Ogre::Vector3 &eyepos, float &pitch, float &yaw) -{ - eyepos = mCamera->getPosition(); - eyepos.z += mCamera->getHeight(); - mCamera->getSightAngles(pitch, yaw); -} - bool RenderingManager::vanityRotateCamera(const float *rot) { if(!mCamera->isVanityOrPreviewModeEnabled()) diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index b492a0db9..2111b89f1 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -87,8 +87,6 @@ public: bool vanityRotateCamera(const float *rot); - void getCameraData(Ogre::Vector3 &eyepos, float &pitch, float &yaw); - void setupPlayer(const MWWorld::Ptr &ptr); void renderPlayer(const MWWorld::Ptr &ptr); diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index 7d63a2362..388152376 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -254,15 +254,12 @@ namespace MWWorld std::pair PhysicsSystem::getFacedHandle (MWWorld::World& world, float queryDistance) { - btVector3 dir(0, 1, 0); - dir = dir.rotate(btVector3(1, 0, 0), mCameraData.pitch); - dir = dir.rotate(btVector3(0, 0, 1), mCameraData.yaw); - dir.setX(-dir.x()); + Ray ray = mRender.getCamera()->getCameraToViewportRay(0.5, 0.5); - btVector3 origin(mCameraData.eyepos.x, - mCameraData.eyepos.y, - mCameraData.eyepos.z); - origin += dir * 5; + Ogre::Vector3 origin_ = ray.getOrigin(); + btVector3 origin(origin_.x, origin_.y, origin_.z); + Ogre::Vector3 dir_ = ray.getDirection().normalisedCopy(); + btVector3 dir(dir_.x, dir_.y, dir_.z); btVector3 dest = origin + dir * queryDistance; std::pair result; @@ -273,15 +270,12 @@ namespace MWWorld std::vector < std::pair > PhysicsSystem::getFacedHandles (float queryDistance) { - btVector3 dir(0, 1, 0); - dir = dir.rotate(btVector3(1, 0, 0), mCameraData.pitch); - dir = dir.rotate(btVector3(0, 0, 1), mCameraData.yaw); - dir.setX(-dir.x()); + Ray ray = mRender.getCamera()->getCameraToViewportRay(0.5, 0.5); - btVector3 origin(mCameraData.eyepos.x, - mCameraData.eyepos.y, - mCameraData.eyepos.z); - origin += dir * 5; + Ogre::Vector3 origin_ = ray.getOrigin(); + btVector3 origin(origin_.x, origin_.y, origin_.z); + Ogre::Vector3 dir_ = ray.getDirection().normalisedCopy(); + btVector3 dir(dir_.x, dir_.y, dir_.z); btVector3 dest = origin + dir * queryDistance; std::vector < std::pair > results; @@ -543,11 +537,4 @@ namespace MWWorld return true; } - - void PhysicsSystem::updateCameraData(const Ogre::Vector3 &eyepos, float pitch, float yaw) - { - mCameraData.eyepos = eyepos; - mCameraData.pitch = pitch; - mCameraData.yaw = yaw; - } } diff --git a/apps/openmw/mwworld/physicssystem.hpp b/apps/openmw/mwworld/physicssystem.hpp index 7b48f880e..d7e853260 100644 --- a/apps/openmw/mwworld/physicssystem.hpp +++ b/apps/openmw/mwworld/physicssystem.hpp @@ -77,13 +77,7 @@ namespace MWWorld bool getObjectAABB(const MWWorld::Ptr &ptr, Ogre::Vector3 &min, Ogre::Vector3 &max); - void updateCameraData(const Ogre::Vector3 &eyepos, float pitch, float yaw); - private: - struct { - Ogre::Vector3 eyepos; - float pitch, yaw; - } mCameraData; OEngine::Render::OgreRenderer &mRender; OEngine::Physic::PhysicEngine* mEngine; diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index cd1eb823d..4d6f50ffd 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -163,13 +163,13 @@ namespace MWWorld { if (!target.isEmpty()) MWMechanics::Security(getPlayer()).pickLock(target, item, resultMessage, resultSound); - anim->play("pickprobe", MWMechanics::Priority_Weapon, MWRender::Animation::Group_UpperBody, true, "start", "stop", 0.0, 0); + anim->play("pickprobe", MWMechanics::Priority_Weapon, MWRender::Animation::Group_UpperBody, true, 1.0f, "start", "stop", 0.0, 0); } else if (item.getTypeName() == typeid(ESM::Probe).name()) { if (!target.isEmpty()) MWMechanics::Security(getPlayer()).probeTrap(target, item, resultMessage, resultSound); - anim->play("pickprobe", MWMechanics::Priority_Weapon, MWRender::Animation::Group_UpperBody, true, "start", "stop", 0.0, 0); + anim->play("pickprobe", MWMechanics::Priority_Weapon, MWRender::Animation::Group_UpperBody, true, 1.0f, "start", "stop", 0.0, 0); } if (!resultMessage.empty()) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 5c7a40050..a479c9a14 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1234,11 +1234,6 @@ namespace MWWorld mWorldScene->update (duration, paused); - float pitch, yaw; - Ogre::Vector3 eyepos; - mRendering->getCameraData(eyepos, pitch, yaw); - mPhysics->updateCameraData(eyepos, pitch, yaw); - performUpdateSceneQueries (); updateWindowManager (); diff --git a/components/esm/loadcell.cpp b/components/esm/loadcell.cpp index b1f9986be..dbd1fed6f 100644 --- a/components/esm/loadcell.cpp +++ b/components/esm/loadcell.cpp @@ -122,7 +122,7 @@ void Cell::save(ESMWriter &esm) void Cell::restore(ESMReader &esm, int iCtx) const { - esm.restoreContext(mContextList[iCtx]); + esm.restoreContext(mContextList.at (iCtx)); } std::string Cell::getDescription() const diff --git a/files/mygui/openmw_book.layout b/files/mygui/openmw_book.layout index d9ac2f43a..3d6322e74 100644 --- a/files/mygui/openmw_book.layout +++ b/files/mygui/openmw_book.layout @@ -4,9 +4,9 @@ - - - + + + @@ -44,6 +44,7 @@ + diff --git a/files/mygui/openmw_console.layout b/files/mygui/openmw_console.layout index 732684ad1..6b0bb9f17 100644 --- a/files/mygui/openmw_console.layout +++ b/files/mygui/openmw_console.layout @@ -4,6 +4,7 @@ + diff --git a/files/mygui/openmw_journal.layout b/files/mygui/openmw_journal.layout index 58d2c2690..34421d431 100644 --- a/files/mygui/openmw_journal.layout +++ b/files/mygui/openmw_journal.layout @@ -4,9 +4,10 @@ - - - + + + + @@ -53,7 +54,7 @@ - + @@ -103,7 +104,6 @@ -