diff --git a/.gitignore b/.gitignore index 3975c4521..08bf0bad6 100644 --- a/.gitignore +++ b/.gitignore @@ -41,7 +41,7 @@ resources ## generated objects apps/openmw/config.hpp components/version/version.hpp -Docs/mainpage.hpp +docs/mainpage.hpp moc_*.cxx *.cxx_parameters *qrc_launcher.cxx diff --git a/CMakeLists.txt b/CMakeLists.txt index 3632f1e08..9d92930c9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,7 +56,7 @@ include(OpenMWMacros) # doxygen main page -configure_file ("${OpenMW_SOURCE_DIR}/Docs/mainpage.hpp.cmake" "${OpenMW_SOURCE_DIR}/Docs/mainpage.hpp") +configure_file ("${OpenMW_SOURCE_DIR}/docs/mainpage.hpp.cmake" "${OpenMW_SOURCE_DIR}/docs/mainpage.hpp") option(MYGUI_STATIC "Link static build of Mygui into the binaries" FALSE) option(OGRE_STATIC "Link static build of Ogre and Ogre Plugins into the binaries" FALSE) @@ -80,13 +80,6 @@ option(USE_FFMPEG "use ffmpeg for sound" ON) # OS X deployment option(OPENMW_OSX_DEPLOYMENT OFF) -if(UNIX AND NOT APPLE) - option(BUILD_WITH_DPKG "enable dpkg-based install for debian and debian derivatives" OFF) - if(BUILD_WITH_DPKG) - find_program(DPKG_PROGRAM dpkg DOC "dpkg program of Debian-based systems") - endif(BUILD_WITH_DPKG) -endif(UNIX AND NOT APPLE) - # Location of morrowind data files if (APPLE) set(MORROWIND_DATA_FILES "./data" CACHE PATH "location of Morrowind data files") @@ -395,46 +388,36 @@ if (CMAKE_COMPILER_IS_GNUCC) endif (CMAKE_COMPILER_IS_GNUCC) IF(NOT WIN32 AND NOT APPLE) - ## Debian and non debian Linux building + # Linux building # Paths - IF (DPKG_PROGRAM) - ## Debian specific - SET(CMAKE_INSTALL_PREFIX "/usr") - SET(DATAROOTDIR "share" CACHE PATH "Sets the root of data directories to a non-default location") - SET(DATADIR "share/games/openmw" CACHE PATH "Sets the openmw data directories to a non-default location") - SET(ICONDIR "share/pixmaps" CACHE PATH "Set icon dir") - SET(SYSCONFDIR "../etc/openmw" CACHE PATH "Set config dir") - ELSE () - ## Non debian specific - SET(BINDIR "${CMAKE_INSTALL_PREFIX}/bin" CACHE PATH "Where to install binaries") - SET(DATAROOTDIR "${CMAKE_INSTALL_PREFIX}/share" CACHE PATH "Sets the root of data directories to a non-default location") - SET(DATADIR "${DATAROOTDIR}/games/openmw" CACHE PATH "Sets the openmw data directories to a non-default location") - SET(ICONDIR "${DATAROOTDIR}/pixmaps" CACHE PATH "Set icon dir") - SET(LICDIR "${DATAROOTDIR}/licenses/openmw" CACHE PATH "Sets the openmw license directory to a non-default location.") - SET(SYSCONFDIR "/etc/openmw" CACHE PATH "Set config dir") - - # Install binaries - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw" DESTINATION "${BINDIR}" ) - IF(BUILD_LAUNCHER) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/omwlauncher" DESTINATION "${BINDIR}" ) - ENDIF(BUILD_LAUNCHER) - IF(BUILD_BSATOOL) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/bsatool" DESTINATION "${BINDIR}" ) - ENDIF(BUILD_BSATOOL) - IF(BUILD_ESMTOOL) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/esmtool" DESTINATION "${BINDIR}" ) - ENDIF(BUILD_ESMTOOL) - IF(BUILD_MWINIIMPORTER) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/mwiniimport" DESTINATION "${BINDIR}" ) - ENDIF(BUILD_MWINIIMPORTER) - IF(BUILD_OPENCS) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/opencs" DESTINATION "${BINDIR}" ) - ENDIF(BUILD_OPENCS) - - # Install licenses - INSTALL(FILES "DejaVu Font License.txt" DESTINATION "${LICDIR}" ) - INSTALL(FILES "extern/shiny/License.txt" DESTINATION "${LICDIR}" RENAME "Shiny License.txt" ) - ENDIF (DPKG_PROGRAM) + SET(BINDIR "${CMAKE_INSTALL_PREFIX}/bin" CACHE PATH "Where to install binaries") + SET(DATAROOTDIR "${CMAKE_INSTALL_PREFIX}/share" CACHE PATH "Sets the root of data directories to a non-default location") + SET(DATADIR "${DATAROOTDIR}/games/openmw" CACHE PATH "Sets the openmw data directories to a non-default location") + SET(ICONDIR "${DATAROOTDIR}/pixmaps" CACHE PATH "Set icon dir") + SET(LICDIR "${DATAROOTDIR}/licenses/openmw" CACHE PATH "Sets the openmw license directory to a non-default location.") + SET(SYSCONFDIR "/etc/openmw" CACHE PATH "Set config dir") + + # Install binaries + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw" DESTINATION "${BINDIR}" ) + IF(BUILD_LAUNCHER) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/omwlauncher" DESTINATION "${BINDIR}" ) + ENDIF(BUILD_LAUNCHER) + IF(BUILD_BSATOOL) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/bsatool" DESTINATION "${BINDIR}" ) + ENDIF(BUILD_BSATOOL) + IF(BUILD_ESMTOOL) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/esmtool" DESTINATION "${BINDIR}" ) + ENDIF(BUILD_ESMTOOL) + IF(BUILD_MWINIIMPORTER) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/mwiniimport" DESTINATION "${BINDIR}" ) + ENDIF(BUILD_MWINIIMPORTER) + IF(BUILD_OPENCS) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/opencs" DESTINATION "${BINDIR}" ) + ENDIF(BUILD_OPENCS) + + # Install licenses + INSTALL(FILES "docs/license/DejaVu Font License.txt" DESTINATION "${LICDIR}" ) + INSTALL(FILES "extern/shiny/License.txt" DESTINATION "${LICDIR}" RENAME "Shiny License.txt" ) # Install icon and desktop file INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.desktop" DESTINATION "${DATAROOTDIR}/applications" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "openmw") diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index ec721a5e5..d7733ba0e 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -119,10 +119,6 @@ endif(NOT WIN32) -if(DPKG_PROGRAM) - INSTALL(TARGETS omwlauncher RUNTIME DESTINATION games COMPONENT omwlauncher) -endif() - if (BUILD_WITH_CODE_COVERAGE) add_definitions (--coverage) target_link_libraries(omwlauncher gcov) diff --git a/apps/mwiniimporter/CMakeLists.txt b/apps/mwiniimporter/CMakeLists.txt index 702f66513..deab88ce2 100644 --- a/apps/mwiniimporter/CMakeLists.txt +++ b/apps/mwiniimporter/CMakeLists.txt @@ -22,8 +22,3 @@ if (BUILD_WITH_CODE_COVERAGE) add_definitions (--coverage) target_link_libraries(mwiniimport gcov) endif() - -if(DPKG_PROGRAM) - INSTALL(TARGETS mwiniimport RUNTIME DESTINATION games COMPONENT mwiniimport) -endif() - diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index f18ac0bca..ddaca7e71 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 regionmap data + idtable idtableproxymodel regionmap data commanddispatcher ) @@ -199,10 +199,6 @@ target_link_libraries(opencs components ) -if(DPKG_PROGRAM) - INSTALL(TARGETS opencs RUNTIME DESTINATION games COMPONENT opencs) -endif() - if(APPLE) INSTALL(TARGETS opencs BUNDLE DESTINATION OpenMW COMPONENT BUNDLE) endif() diff --git a/apps/opencs/model/world/commanddispatcher.cpp b/apps/opencs/model/world/commanddispatcher.cpp new file mode 100644 index 000000000..4e146d87c --- /dev/null +++ b/apps/opencs/model/world/commanddispatcher.cpp @@ -0,0 +1,267 @@ + +#include "commanddispatcher.hpp" + +#include + +#include + +#include "../doc/document.hpp" + +#include "idtable.hpp" +#include "record.hpp" +#include "commands.hpp" + +std::vector CSMWorld::CommandDispatcher::getDeletableRecords() const +{ + std::vector result; + + IdTable& model = dynamic_cast (*mDocument.getData().getTableModel (mId)); + + int stateColumnIndex = model.findColumnIndex (Columns::ColumnId_Modification); + + for (std::vector::const_iterator iter (mSelection.begin()); + iter!=mSelection.end(); ++iter) + { + int row = model.getModelIndex (*iter, 0).row(); + + // check record state + RecordBase::State state = static_cast ( + model.data (model.index (row, stateColumnIndex)).toInt()); + + if (state==RecordBase::State_Deleted) + continue; + + // check other columns (only relevant for a subset of the tables) + int dialogueTypeIndex = model.searchColumnIndex (Columns::ColumnId_DialogueType); + + if (dialogueTypeIndex!=-1) + { + int type = model.data (model.index (row, dialogueTypeIndex)).toInt(); + + if (type!=ESM::Dialogue::Topic && type!=ESM::Dialogue::Journal) + continue; + } + + result.push_back (*iter); + } + + return result; +} + +std::vector CSMWorld::CommandDispatcher::getRevertableRecords() const +{ + std::vector result; + + IdTable& model = dynamic_cast (*mDocument.getData().getTableModel (mId)); + + /// \todo Reverting temporarily disabled on tables that support reordering, because + /// revert logic currently can not handle reordering. + if (model.getFeatures() & IdTable::Feature_ReorderWithinTopic) + return result; + + int stateColumnIndex = model.findColumnIndex (Columns::ColumnId_Modification); + + for (std::vector::const_iterator iter (mSelection.begin()); + iter!=mSelection.end(); ++iter) + { + int row = model.getModelIndex (*iter, 0).row(); + + // check record state + RecordBase::State state = static_cast ( + model.data (model.index (row, stateColumnIndex)).toInt()); + + if (state==RecordBase::State_BaseOnly) + continue; + + result.push_back (*iter); + } + + return result; +} + +CSMWorld::CommandDispatcher::CommandDispatcher (CSMDoc::Document& document, + const CSMWorld::UniversalId& id, QObject *parent) +: QObject (parent), mDocument (document), mId (id), mLocked (false) +{} + +void CSMWorld::CommandDispatcher::setEditLock (bool locked) +{ + mLocked = locked; +} + +void CSMWorld::CommandDispatcher::setSelection (const std::vector& selection) +{ + mSelection = selection; + std::for_each (mSelection.begin(), mSelection.end(), Misc::StringUtils::toLower); + std::sort (mSelection.begin(), mSelection.end()); +} + +void CSMWorld::CommandDispatcher::setExtendedTypes (const std::vector& types) +{ + mExtendedTypes = types; +} + +bool CSMWorld::CommandDispatcher::canDelete() const +{ + if (mLocked) + return false; + + return getDeletableRecords().size()!=0; +} + +bool CSMWorld::CommandDispatcher::canRevert() const +{ + if (mLocked) + return false; + + return getRevertableRecords().size()!=0; +} + +std::vector CSMWorld::CommandDispatcher::getExtendedTypes() const +{ + std::vector tables; + + if (mId==UniversalId::Type_Cells) + { + tables.push_back (mId); + tables.push_back (UniversalId::Type_References); + /// \todo add other cell-specific types + } + + return tables; +} + +void CSMWorld::CommandDispatcher::executeDelete() +{ + if (mLocked) + return; + + std::vector rows = getDeletableRecords(); + + if (rows.empty()) + return; + + IdTable& model = dynamic_cast (*mDocument.getData().getTableModel (mId)); + + int columnIndex = model.findColumnIndex (Columns::ColumnId_Id); + + if (rows.size()>1) + mDocument.getUndoStack().beginMacro (tr ("Delete multiple records")); + + for (std::vector::const_iterator iter (rows.begin()); iter!=rows.end(); ++iter) + { + std::string id = model.data (model.getModelIndex (*iter, columnIndex)). + toString().toUtf8().constData(); + + mDocument.getUndoStack().push (new CSMWorld::DeleteCommand (model, id)); + } + + if (rows.size()>1) + mDocument.getUndoStack().endMacro(); +} + +void CSMWorld::CommandDispatcher::executeRevert() +{ + if (mLocked) + return; + + std::vector rows = getRevertableRecords(); + + if (rows.empty()) + return; + + IdTable& model = dynamic_cast (*mDocument.getData().getTableModel (mId)); + + int columnIndex = model.findColumnIndex (Columns::ColumnId_Id); + + if (rows.size()>1) + mDocument.getUndoStack().beginMacro (tr ("Revert multiple records")); + + for (std::vector::const_iterator iter (rows.begin()); iter!=rows.end(); ++iter) + { + std::string id = model.data (model.getModelIndex (*iter, columnIndex)). + toString().toUtf8().constData(); + + mDocument.getUndoStack().push (new CSMWorld::RevertCommand (model, id)); + } + + if (rows.size()>1) + mDocument.getUndoStack().endMacro(); +} + +void CSMWorld::CommandDispatcher::executeExtendedDelete() +{ + if (mExtendedTypes.size()>1) + mDocument.getUndoStack().beginMacro (tr ("Extended delete of multiple records")); + + for (std::vector::const_iterator iter (mExtendedTypes.begin()); + iter!=mExtendedTypes.end(); ++iter) + { + if (*iter==mId) + executeDelete(); + else if (*iter==UniversalId::Type_References) + { + IdTable& model = dynamic_cast ( + *mDocument.getData().getTableModel (*iter)); + + const RefCollection& collection = mDocument.getData().getReferences(); + + int size = collection.getSize(); + + for (int i=size-1; i>=0; --i) + { + const Record& record = collection.getRecord (i); + + if (record.mState==RecordBase::State_Deleted) + continue; + + if (!std::binary_search (mSelection.begin(), mSelection.end(), + Misc::StringUtils::lowerCase (record.get().mCell))) + continue; + + mDocument.getUndoStack().push ( + new CSMWorld::DeleteCommand (model, record.get().mId)); + } + } + } + + if (mExtendedTypes.size()>1) + mDocument.getUndoStack().endMacro(); +} + +void CSMWorld::CommandDispatcher::executeExtendedRevert() +{ + if (mExtendedTypes.size()>1) + mDocument.getUndoStack().beginMacro (tr ("Extended revert of multiple records")); + + for (std::vector::const_iterator iter (mExtendedTypes.begin()); + iter!=mExtendedTypes.end(); ++iter) + { + if (*iter==mId) + executeRevert(); + else if (*iter==UniversalId::Type_References) + { + IdTable& model = dynamic_cast ( + *mDocument.getData().getTableModel (*iter)); + + const RefCollection& collection = mDocument.getData().getReferences(); + + int size = collection.getSize(); + + for (int i=size-1; i>=0; --i) + { + const Record& record = collection.getRecord (i); + + if (!std::binary_search (mSelection.begin(), mSelection.end(), + Misc::StringUtils::lowerCase (record.get().mCell))) + continue; + + mDocument.getUndoStack().push ( + new CSMWorld::RevertCommand (model, record.get().mId)); + } + } + } + + if (mExtendedTypes.size()>1) + mDocument.getUndoStack().endMacro(); +} \ No newline at end of file diff --git a/apps/opencs/model/world/commanddispatcher.hpp b/apps/opencs/model/world/commanddispatcher.hpp new file mode 100644 index 000000000..50085b1a1 --- /dev/null +++ b/apps/opencs/model/world/commanddispatcher.hpp @@ -0,0 +1,69 @@ +#ifndef CSM_WOLRD_COMMANDDISPATCHER_H +#define CSM_WOLRD_COMMANDDISPATCHER_H + +#include + +#include + +#include "universalid.hpp" + +namespace CSMDoc +{ + class Document; +} + +namespace CSMWorld +{ + class CommandDispatcher : public QObject + { + Q_OBJECT + + bool mLocked; + CSMDoc::Document& mDocument; + UniversalId mId; + std::vector mSelection; + std::vector mExtendedTypes; + + std::vector getDeletableRecords() const; + + std::vector getRevertableRecords() const; + + public: + + CommandDispatcher (CSMDoc::Document& document, const CSMWorld::UniversalId& id, + QObject *parent = 0); + ///< \param id ID of the table the commands should operate on primarily. + + void setEditLock (bool locked); + + void setSelection (const std::vector& selection); + + void setExtendedTypes (const std::vector& types); + ///< Set record lists selected by the user for extended operations. + + bool canDelete() const; + + bool canRevert() const; + + /// Return IDs of the record collection that can also be affected when + /// operating on the record collection this dispatcher is used for. + /// + /// \note The returned collection contains the ID of the record collection this + /// dispatcher is used for. However if that record collection does not support + /// the extended mode, the returned vector will be empty instead. + std::vector getExtendedTypes() const; + + public slots: + + void executeDelete(); + + void executeRevert(); + + void executeExtendedDelete(); + + void executeExtendedRevert(); + + }; +} + +#endif diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 442e73e51..7f2eac20d 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -16,11 +16,12 @@ #include "regionmap.hpp" #include "columns.hpp" -void CSMWorld::Data::addModel (QAbstractItemModel *model, UniversalId::Type type1, - UniversalId::Type type2, bool update) +void CSMWorld::Data::addModel (QAbstractItemModel *model, UniversalId::Type type, bool update) { mModels.push_back (model); - mModelIndex.insert (std::make_pair (type1, model)); + mModelIndex.insert (std::make_pair (type, model)); + + UniversalId::Type type2 = UniversalId::getParentType (type); if (type2!=UniversalId::Type_None) mModelIndex.insert (std::make_pair (type2, model)); @@ -235,26 +236,26 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding) mFilters.addColumn (new DescriptionColumn); mFilters.addColumn (new ScopeColumn); - addModel (new IdTable (&mGlobals), UniversalId::Type_Globals, UniversalId::Type_Global); - addModel (new IdTable (&mGmsts), UniversalId::Type_Gmsts, UniversalId::Type_Gmst); - addModel (new IdTable (&mSkills), UniversalId::Type_Skills, UniversalId::Type_Skill, false); - addModel (new IdTable (&mClasses), UniversalId::Type_Classes, UniversalId::Type_Class); - addModel (new IdTable (&mFactions), UniversalId::Type_Factions, UniversalId::Type_Faction); - addModel (new IdTable (&mRaces), UniversalId::Type_Races, UniversalId::Type_Race); - addModel (new IdTable (&mSounds), UniversalId::Type_Sounds, UniversalId::Type_Sound); - addModel (new IdTable (&mScripts), UniversalId::Type_Scripts, UniversalId::Type_Script); - addModel (new IdTable (&mRegions), UniversalId::Type_Regions, UniversalId::Type_Region); - addModel (new IdTable (&mBirthsigns), UniversalId::Type_Birthsigns, UniversalId::Type_Birthsign); - addModel (new IdTable (&mSpells), UniversalId::Type_Spells, UniversalId::Type_Spell); - addModel (new IdTable (&mTopics), UniversalId::Type_Topics, UniversalId::Type_Topic); - addModel (new IdTable (&mJournals), UniversalId::Type_Journals, UniversalId::Type_Journal); - addModel (new IdTable (&mTopicInfos, IdTable::Reordering_WithinTopic), UniversalId::Type_TopicInfos, UniversalId::Type_TopicInfo); - addModel (new IdTable (&mJournalInfos, IdTable::Reordering_WithinTopic), UniversalId::Type_JournalInfos, UniversalId::Type_JournalInfo); - addModel (new IdTable (&mCells, IdTable::Reordering_None, IdTable::Viewing_Id), UniversalId::Type_Cells, UniversalId::Type_Cell); - addModel (new IdTable (&mReferenceables, IdTable::Reordering_None, IdTable::Viewing_None, true), - UniversalId::Type_Referenceables, UniversalId::Type_Referenceable); - addModel (new IdTable (&mRefs, IdTable::Reordering_None, IdTable::Viewing_Cell, true), UniversalId::Type_References, UniversalId::Type_Reference, false); - addModel (new IdTable (&mFilters), UniversalId::Type_Filters, UniversalId::Type_Filter, false); + addModel (new IdTable (&mGlobals), UniversalId::Type_Global); + addModel (new IdTable (&mGmsts), UniversalId::Type_Gmst); + addModel (new IdTable (&mSkills), UniversalId::Type_Skill); + addModel (new IdTable (&mClasses), UniversalId::Type_Class); + addModel (new IdTable (&mFactions), UniversalId::Type_Faction); + addModel (new IdTable (&mRaces), UniversalId::Type_Race); + addModel (new IdTable (&mSounds), UniversalId::Type_Sound); + addModel (new IdTable (&mScripts), UniversalId::Type_Script); + addModel (new IdTable (&mRegions), UniversalId::Type_Region); + addModel (new IdTable (&mBirthsigns), UniversalId::Type_Birthsign); + addModel (new IdTable (&mSpells), UniversalId::Type_Spell); + addModel (new IdTable (&mTopics), UniversalId::Type_Topic); + addModel (new IdTable (&mJournals), UniversalId::Type_Journal); + addModel (new IdTable (&mTopicInfos, IdTable::Feature_ReorderWithinTopic), UniversalId::Type_TopicInfo); + addModel (new IdTable (&mJournalInfos, IdTable::Feature_ReorderWithinTopic), UniversalId::Type_JournalInfo); + addModel (new IdTable (&mCells, IdTable::Feature_ViewId), UniversalId::Type_Cell); + addModel (new IdTable (&mReferenceables, IdTable::Feature_Preview), + UniversalId::Type_Referenceable); + addModel (new IdTable (&mRefs, IdTable::Feature_ViewCell | IdTable::Feature_Preview), UniversalId::Type_Reference); + addModel (new IdTable (&mFilters), UniversalId::Type_Filter); } CSMWorld::Data::~Data() @@ -469,8 +470,7 @@ QAbstractItemModel *CSMWorld::Data::getTableModel (const CSMWorld::UniversalId& if (id.getType()==UniversalId::Type_RegionMap) { RegionMap *table = 0; - addModel (table = new RegionMap (*this), UniversalId::Type_RegionMap, - UniversalId::Type_None, false); + addModel (table = new RegionMap (*this), UniversalId::Type_RegionMap, false); return table; } throw std::logic_error ("No table model available for " + id.toString()); diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index 9a842878a..edca9fdbc 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -83,8 +83,8 @@ namespace CSMWorld Data (const Data&); Data& operator= (const Data&); - void addModel (QAbstractItemModel *model, UniversalId::Type type1, - UniversalId::Type type2 = UniversalId::Type_None, bool update = true); + void addModel (QAbstractItemModel *model, UniversalId::Type type, + bool update = true); static void appendIds (std::vector& ids, const CollectionBase& collection, bool listDeleted); diff --git a/apps/opencs/model/world/idtable.cpp b/apps/opencs/model/world/idtable.cpp index 50998c36f..a481ce143 100644 --- a/apps/opencs/model/world/idtable.cpp +++ b/apps/opencs/model/world/idtable.cpp @@ -4,9 +4,8 @@ #include "collectionbase.hpp" #include "columnbase.hpp" -CSMWorld::IdTable::IdTable (CollectionBase *idCollection, Reordering reordering, - Viewing viewing, bool preview) -: mIdCollection (idCollection), mReordering (reordering), mViewing (viewing), mPreview (preview) +CSMWorld::IdTable::IdTable (CollectionBase *idCollection, unsigned int features) +: mIdCollection (idCollection), mFeatures (features) {} CSMWorld::IdTable::~IdTable() @@ -186,19 +185,9 @@ void CSMWorld::IdTable::reorderRows (int baseIndex, const std::vector& newO index (baseIndex+newOrder.size()-1, mIdCollection->getColumns()-1)); } -CSMWorld::IdTable::Reordering CSMWorld::IdTable::getReordering() const +unsigned int CSMWorld::IdTable::getFeatures() const { - return mReordering; -} - -CSMWorld::IdTable::Viewing CSMWorld::IdTable::getViewing() const -{ - return mViewing; -} - -bool CSMWorld::IdTable::hasPreview() const -{ - return mPreview; + return mFeatures; } std::pair CSMWorld::IdTable::view (int row) const @@ -206,7 +195,7 @@ std::pair CSMWorld::IdTable::view (int row) std::string id; std::string hint; - if (mViewing==Viewing_Cell) + if (mFeatures & Feature_ViewCell) { int cellColumn = mIdCollection->searchColumnIndex (Columns::ColumnId_Cell); int idColumn = mIdCollection->searchColumnIndex (Columns::ColumnId_Id); @@ -217,7 +206,7 @@ std::pair CSMWorld::IdTable::view (int row) hint = "r:" + std::string (mIdCollection->getData (row, idColumn).toString().toUtf8().constData()); } } - else if (mViewing==Viewing_Id) + else if (mFeatures & Feature_ViewId) { int column = mIdCollection->searchColumnIndex (Columns::ColumnId_Id); diff --git a/apps/opencs/model/world/idtable.hpp b/apps/opencs/model/world/idtable.hpp index 8b5462825..5500d40b8 100644 --- a/apps/opencs/model/world/idtable.hpp +++ b/apps/opencs/model/world/idtable.hpp @@ -19,26 +19,27 @@ namespace CSMWorld public: - enum Reordering + enum Features { - Reordering_None, - Reordering_WithinTopic - }; + Feature_ReorderWithinTopic = 1, - enum Viewing - { - Viewing_None, - Viewing_Id, // use ID column to generate view request (ID is transformed into - // worldspace and original ID is passed as hint with c: prefix) - Viewing_Cell // use cell column to generate view request (cell ID is transformed - // into worldspace and record ID is passed as hint with r: prefix) + /// Use ID column to generate view request (ID is transformed into + /// worldspace and original ID is passed as hint with c: prefix). + Feature_ViewId = 2, + + /// Use cell column to generate view request (cell ID is transformed + /// into worldspace and record ID is passed as hint with r: prefix). + Feature_ViewCell = 4, + + Feature_View = Feature_ViewId | Feature_ViewCell, + + Feature_Preview = 8 }; private: CollectionBase *mIdCollection; - Reordering mReordering; - Viewing mViewing; + unsigned int mFeatures; bool mPreview; // not implemented @@ -47,8 +48,7 @@ namespace CSMWorld public: - IdTable (CollectionBase *idCollection, Reordering reordering = Reordering_None, - Viewing viewing = Viewing_None, bool preview = false); + IdTable (CollectionBase *idCollection, unsigned int features = 0); ///< The ownership of \a idCollection is not transferred. virtual ~IdTable(); @@ -97,11 +97,7 @@ namespace CSMWorld ///< Reorder the rows [baseIndex, baseIndex+newOrder.size()) according to the indices /// given in \a newOrder (baseIndex+newOrder[0] specifies the new index of row baseIndex). - Reordering getReordering() const; - - Viewing getViewing() const; - - bool hasPreview() const; + unsigned int getFeatures() const; std::pair view (int row) const; ///< Return the UniversalId and the hint for viewing \a row. If viewing is not diff --git a/apps/opencs/model/world/universalid.cpp b/apps/opencs/model/world/universalid.cpp index 140a410c0..88e649ace 100644 --- a/apps/opencs/model/world/universalid.cpp +++ b/apps/opencs/model/world/universalid.cpp @@ -61,8 +61,8 @@ namespace { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Spell, "Spell", ":./spell.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Topic, "Topic", 0 }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Journal, "Journal", 0 }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_TopicInfo, "TopicInfo", 0 }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_JournalInfo, "JournalInfo", 0 }, + { CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_TopicInfo, "TopicInfo", 0 }, + { CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_JournalInfo, "JournalInfo", 0 }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Cell, "Cell", ":./cell.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Cell_Missing, "Cell", ":./cell.png" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Referenceable, "Referenceables", 0 }, @@ -90,7 +90,7 @@ namespace { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Static, "Static", ":./static.png" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Weapon, "Weapon", ":./weapon.png" }, { CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_Reference, "Reference", 0 }, - { CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_Filter, "Filter", ":./filter.png" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Filter, "Filter", ":./filter.png" }, { CSMWorld::UniversalId::Class_Collection, CSMWorld::UniversalId::Type_Scene, "Scene", 0 }, { CSMWorld::UniversalId::Class_Collection, CSMWorld::UniversalId::Type_Preview, "Preview", 0 }, @@ -320,6 +320,28 @@ std::vector CSMWorld::UniversalId::listReferenceabl return list; } +CSMWorld::UniversalId::Type CSMWorld::UniversalId::getParentType (Type type) +{ + for (int i=0; sIdArg[i].mType; ++i) + if (type==sIdArg[i].mType) + { + if (sIdArg[i].mClass==Class_RefRecord) + return Type_Referenceables; + + if (sIdArg[i].mClass==Class_SubRecord || sIdArg[i].mClass==Class_Record) + { + if (type==Type_Cell_Missing) + return Type_Cells; + + return static_cast (type-1); + } + + break; + } + + return Type_None; +} + bool CSMWorld::operator== (const CSMWorld::UniversalId& left, const CSMWorld::UniversalId& right) { return left.isEqual (right); diff --git a/apps/opencs/model/world/universalid.hpp b/apps/opencs/model/world/universalid.hpp index 22779b263..3bef71c75 100644 --- a/apps/opencs/model/world/universalid.hpp +++ b/apps/opencs/model/world/universalid.hpp @@ -32,6 +32,8 @@ namespace CSMWorld ArgumentType_Index }; + /// \note A record list type must always be immediately followed by the matching + /// record type, if this type is of class SubRecord or Record. enum Type { Type_None = 0, @@ -86,8 +88,8 @@ namespace CSMWorld Type_References, Type_Reference, Type_RegionMap, - Type_Filter, Type_Filters, + Type_Filter, Type_Topics, Type_Topic, Type_Journals, @@ -147,6 +149,11 @@ namespace CSMWorld ///< Will return an empty string, if no icon is available. static std::vector listReferenceableTypes(); + + /// If \a type is a SubRecord, RefRecord or Record type return the type of the table + /// that contains records of type \a type. + /// Otherwise return Type_None. + static Type getParentType (Type type); }; bool operator== (const UniversalId& left, const UniversalId& right); diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index d03bf3f80..ad2db8723 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -332,6 +332,7 @@ void CSVWorld::EditWidget::remake(int row) if (mMainWidget) { delete mMainWidget; + mMainWidget = 0; } mMainWidget = new QWidget (this); @@ -339,6 +340,7 @@ void CSVWorld::EditWidget::remake(int row) if (mWidgetMapper) { delete mWidgetMapper; + mWidgetMapper = 0; } mWidgetMapper = new QDataWidgetMapper (this); mWidgetMapper->setModel(mTable); @@ -396,7 +398,7 @@ void CSVWorld::EditWidget::remake(int row) mWidgetMapper->setCurrentModelIndex(mTable->index(row, 0)); - this->setMinimumWidth(325); //TODO find better way to set the width or make it customizable + this->setMinimumWidth(325); /// \todo replace hardcoded value with a user setting this->setWidget(mMainWidget); this->setWidgetResizable(true); } @@ -407,7 +409,6 @@ void CSVWorld::EditWidget::remake(int row) CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document, const CreatorFactoryBase& creatorFactory, bool sorting) : - SubView (id), mEditWidget(0), mMainLayout(NULL), @@ -415,8 +416,8 @@ CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, CSM mTable(dynamic_cast(document.getData().getTableModel(id))), mRow (-1), mLocked(false), - mDocument(document) - + mDocument(document), + mCommandDispatcher (document, CSMWorld::UniversalId::getParentType (id.getType())) { connect(mTable, SIGNAL(dataChanged (const QModelIndex&, const QModelIndex&)), this, SLOT(dataChanged(const QModelIndex&))); mRow = mTable->getModelIndex (id.getId(), 0).row(); @@ -440,7 +441,7 @@ CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, CSM QToolButton* revertButton = new QToolButton(mainWidget); revertButton->setIcon(QIcon(":/edit-undo.png")); - if (mTable->hasPreview()) + if (mTable->getFeatures() & CSMWorld::IdTable::Feature_Preview) { QToolButton* previewButton = new QToolButton(mainWidget); previewButton->setIcon(QIcon(":/edit-preview.png")); @@ -448,7 +449,7 @@ CSVWorld::DialogueSubView::DialogueSubView (const CSMWorld::UniversalId& id, CSM connect(previewButton, SIGNAL(clicked()), this, SLOT(showPreview())); } - if (mTable->getViewing()!=CSMWorld::IdTable::Viewing_None) + if (mTable->getFeatures() & CSMWorld::IdTable::Feature_View) { QToolButton* viewButton = new QToolButton(mainWidget); viewButton->setIcon(QIcon(":/cell.png")); @@ -560,14 +561,12 @@ void CSVWorld::DialogueSubView::nextId() void CSVWorld::DialogueSubView::setEditLock (bool locked) { mLocked = locked; + CSMWorld::RecordBase::State state = static_cast(mTable->data (mTable->index (mRow, 1)).toInt()); - if (state == CSMWorld::RecordBase::State_Deleted || state == CSMWorld::RecordBase::State_Erased) - { - mEditWidget->setDisabled(true); - } else - { - mEditWidget->setDisabled(mLocked); - } + + mEditWidget->setDisabled (state==CSMWorld::RecordBase::State_Deleted || locked); + + mCommandDispatcher.setEditLock (locked); } void CSVWorld::DialogueSubView::dataChanged(const QModelIndex & index) @@ -575,13 +574,8 @@ void CSVWorld::DialogueSubView::dataChanged(const QModelIndex & index) if (index.row() == mRow) { CSMWorld::RecordBase::State state = static_cast(mTable->data (mTable->index (mRow, 1)).toInt()); - if (state == CSMWorld::RecordBase::State_Deleted || state == CSMWorld::RecordBase::State_Erased) - { - mEditWidget->setDisabled(true); - } else - { - mEditWidget->setDisabled(mLocked); - } + + mEditWidget->setDisabled (state==CSMWorld::RecordBase::State_Deleted || mLocked); } } @@ -671,7 +665,8 @@ void CSVWorld::DialogueSubView::cloneRequest () void CSVWorld::DialogueSubView::showPreview () { - if (mTable->hasPreview() && mRow < mTable->rowCount()) + if ((mTable->getFeatures() & CSMWorld::IdTable::Feature_Preview) && + mRow < mTable->rowCount()) { emit focusId(CSMWorld::UniversalId(CSMWorld::UniversalId::Type_Preview, mTable->data(mTable->index (mRow, 0)).toString().toUtf8().constData()), ""); } diff --git a/apps/opencs/view/world/dialoguesubview.hpp b/apps/opencs/view/world/dialoguesubview.hpp index 5642f46a0..cbca0159c 100644 --- a/apps/opencs/view/world/dialoguesubview.hpp +++ b/apps/opencs/view/world/dialoguesubview.hpp @@ -8,7 +8,9 @@ #include #include "../doc/subview.hpp" + #include "../../model/world/columnbase.hpp" +#include "../../model/world/commanddispatcher.hpp" class QDataWidgetMapper; class QSize; @@ -169,6 +171,7 @@ namespace CSVWorld bool mLocked; const CSMDoc::Document& mDocument; TableBottomBox* mBottom; + CSMWorld::CommandDispatcher mCommandDispatcher; public: diff --git a/apps/opencs/view/world/subviews.cpp b/apps/opencs/view/world/subviews.cpp index 75f391699..022d5d02e 100644 --- a/apps/opencs/view/world/subviews.cpp +++ b/apps/opencs/view/world/subviews.cpp @@ -80,30 +80,35 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) manager.add (CSMWorld::UniversalId::Type_Scene, new CSVDoc::SubViewFactory); - //edit subviews - manager.add (CSMWorld::UniversalId::Type_Region, - new CSVDoc::SubViewFactoryWithCreator > (false)); - - manager.add (CSMWorld::UniversalId::Type_Spell, - new CSVDoc::SubViewFactoryWithCreator > (false)); + // Dialogue subviews + static const CSMWorld::UniversalId::Type sTableTypes2[] = + { + CSMWorld::UniversalId::Type_Region, + CSMWorld::UniversalId::Type_Spell, + CSMWorld::UniversalId::Type_Birthsign, + CSMWorld::UniversalId::Type_Global, + CSMWorld::UniversalId::Type_Race, + CSMWorld::UniversalId::Type_Class, + CSMWorld::UniversalId::Type_Filter, + CSMWorld::UniversalId::Type_Sound, + CSMWorld::UniversalId::Type_Faction, - manager.add (CSMWorld::UniversalId::Type_Referenceable, - new CSVDoc::SubViewFactoryWithCreator > (false)); + CSMWorld::UniversalId::Type_None // end marker + }; - manager.add (CSMWorld::UniversalId::Type_Birthsign, - new CSVDoc::SubViewFactoryWithCreator > (false)); + for (int i=0; sTableTypes2[i]!=CSMWorld::UniversalId::Type_None; ++i) + manager.add (sTableTypes2[i], + new CSVDoc::SubViewFactoryWithCreator > (false)); - manager.add (CSMWorld::UniversalId::Type_Global, - new CSVDoc::SubViewFactoryWithCreator > (false)); + manager.add (CSMWorld::UniversalId::Type_Skill, + new CSVDoc::SubViewFactoryWithCreator (false)); manager.add (CSMWorld::UniversalId::Type_Gmst, - new CSVDoc::SubViewFactoryWithCreator > (false)); - - manager.add (CSMWorld::UniversalId::Type_Race, - new CSVDoc::SubViewFactoryWithCreator > (false)); + new CSVDoc::SubViewFactoryWithCreator (false)); - manager.add (CSMWorld::UniversalId::Type_Class, - new CSVDoc::SubViewFactoryWithCreator > (false)); + manager.add (CSMWorld::UniversalId::Type_Referenceable, + new CSVDoc::SubViewFactoryWithCreator > (false)); manager.add (CSMWorld::UniversalId::Type_Reference, new CSVDoc::SubViewFactoryWithCreator > (false)); @@ -111,18 +116,6 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) manager.add (CSMWorld::UniversalId::Type_Cell, new CSVDoc::SubViewFactoryWithCreator > (false)); - manager.add (CSMWorld::UniversalId::Type_Filter, - new CSVDoc::SubViewFactoryWithCreator > (false)); - - manager.add (CSMWorld::UniversalId::Type_Sound, - new CSVDoc::SubViewFactoryWithCreator > (false)); - - manager.add (CSMWorld::UniversalId::Type_Faction, - new CSVDoc::SubViewFactoryWithCreator > (false)); - - manager.add (CSMWorld::UniversalId::Type_Skill, - new CSVDoc::SubViewFactoryWithCreator > (false)); - manager.add (CSMWorld::UniversalId::Type_JournalInfo, new CSVDoc::SubViewFactoryWithCreator > (false)); diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 3d4b02c9c..877fd51c0 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -19,14 +19,36 @@ #include "../../model/world/columns.hpp" #include "../../model/world/tablemimedata.hpp" #include "../../model/world/tablemimedata.hpp" +#include "../../model/world/commanddispatcher.hpp" #include "recordstatusdelegate.hpp" #include "util.hpp" void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) { + // configure dispatcher QModelIndexList selectedRows = selectionModel()->selectedRows(); + std::vector records; + + int columnIndex = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); + + for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); + ++iter) + { + int row = mProxyModel->mapToSource (mProxyModel->index (iter->row(), 0)).row(); + + records.push_back (mModel->data ( + mModel->index (row, columnIndex)).toString().toUtf8().constData()); + } + + mDispatcher->setSelection (records); + + std::vector extendedTypes = mDispatcher->getExtendedTypes(); + + mDispatcher->setExtendedTypes (extendedTypes); + + // create context menu QMenu menu (this); /// \todo add menu items for select all and clear selection @@ -44,22 +66,27 @@ void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) if (mCreateAction) menu.addAction (mCreateAction); - /// \todo Reverting temporarily disabled on tables that support reordering, because - /// revert logic currently can not handle reordering. - if (mModel->getReordering()==CSMWorld::IdTable::Reordering_None) - if (listRevertableSelectedIds().size()>0) - menu.addAction (mRevertAction); + if (mDispatcher->canRevert()) + { + menu.addAction (mRevertAction); - if (listDeletableSelectedIds().size()>0) + if (!extendedTypes.empty()) + menu.addAction (mExtendedRevertAction); + } + + if (mDispatcher->canDelete()) + { menu.addAction (mDeleteAction); - if (mModel->getReordering()==CSMWorld::IdTable::Reordering_WithinTopic) + if (!extendedTypes.empty()) + menu.addAction (mExtendedDeleteAction); + } + + if (mModel->getFeatures() & CSMWorld::IdTable::Feature_ReorderWithinTopic) { /// \todo allow reordering of multiple rows if (selectedRows.size()==1) { - int row =selectedRows.begin()->row(); - int column = mModel->searchColumnIndex (CSMWorld::Columns::ColumnId_Topic); if (column==-1) @@ -67,14 +94,17 @@ void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) if (column!=-1) { - if (row>0 && mProxyModel->data (mProxyModel->index (row, column))== - mProxyModel->data (mProxyModel->index (row-1, column))) + int row = mProxyModel->mapToSource ( + mProxyModel->index (selectedRows.begin()->row(), 0)).row(); + + if (row>0 && mModel->data (mModel->index (row, column))== + mModel->data (mModel->index (row-1, column))) { menu.addAction (mMoveUpAction); } - if (rowrowCount()-1 && mProxyModel->data (mProxyModel->index (row, column))== - mProxyModel->data (mProxyModel->index (row+1, column))) + if (rowrowCount()-1 && mModel->data (mModel->index (row, column))== + mModel->data (mModel->index (row+1, column))) { menu.addAction (mMoveDownAction); } @@ -85,7 +115,7 @@ void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) if (selectedRows.size()==1) { - if (mModel->getViewing()!=CSMWorld::IdTable::Viewing_None) + if (mModel->getFeatures() & CSMWorld::IdTable::Feature_View) { int row = selectedRows.begin()->row(); @@ -101,91 +131,13 @@ void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) menu.addAction (mViewAction); } - if (mModel->hasPreview()) + if (mModel->getFeatures() & CSMWorld::IdTable::Feature_Preview) menu.addAction (mPreviewAction); } menu.exec (event->globalPos()); } -std::vector CSVWorld::Table::listRevertableSelectedIds() const -{ - std::vector revertableIds; - - if (mProxyModel->columnCount()>0) - { - QModelIndexList selectedRows = selectionModel()->selectedRows(); - - for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); - ++iter) - { - QModelIndex index = mProxyModel->mapToSource (mProxyModel->index (iter->row(), 0)); - - CSMWorld::RecordBase::State state = - static_cast ( - mModel->data (mModel->index (index.row(), 1)).toInt()); - - if (state!=CSMWorld::RecordBase::State_BaseOnly) - { - int columnIndex = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); - - std::string id = mModel->data (mModel->index (index.row(), columnIndex)). - toString().toUtf8().constData(); - - revertableIds.push_back (id); - } - } - } - - return revertableIds; -} - -std::vector CSVWorld::Table::listDeletableSelectedIds() const -{ - std::vector deletableIds; - - if (mProxyModel->columnCount()>0) - { - QModelIndexList selectedRows = selectionModel()->selectedRows(); - - for (QModelIndexList::const_iterator iter (selectedRows.begin()); iter!=selectedRows.end(); - ++iter) - { - QModelIndex index = mProxyModel->mapToSource (mProxyModel->index (iter->row(), 0)); - - // check record state - CSMWorld::RecordBase::State state = - static_cast ( - mModel->data (mModel->index (index.row(), 1)).toInt()); - - if (state==CSMWorld::RecordBase::State_Deleted) - continue; - - // check other columns (only relevant for a subset of the tables) - int dialogueTypeIndex = - mModel->searchColumnIndex (CSMWorld::Columns::ColumnId_DialogueType); - - if (dialogueTypeIndex!=-1) - { - int type = mModel->data (mModel->index (index.row(), dialogueTypeIndex)).toInt(); - - if (type!=ESM::Dialogue::Topic && type!=ESM::Dialogue::Journal) - continue; - } - - // add the id to the collection - int columnIndex = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); - - std::string id = mModel->data (mModel->index (index.row(), columnIndex)). - toString().toUtf8().constData(); - - deletableIds.push_back (id); - } - } - - return deletableIds; -} - CSVWorld::Table::Table (const CSMWorld::UniversalId& id, bool createAndDelete, bool sorting, CSMDoc::Document& document) : mCreateAction (0), mCloneAction(0), mRecordStatusDisplay (0), @@ -196,6 +148,8 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, mProxyModel = new CSMWorld::IdTableProxyModel (this); mProxyModel->setSourceModel (mModel); + mDispatcher = new CSMWorld::CommandDispatcher (document, id, this); + setModel (mProxyModel); horizontalHeader()->setResizeMode (QHeaderView::Interactive); verticalHeader()->hide(); @@ -240,11 +194,11 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, } mRevertAction = new QAction (tr ("Revert Record"), this); - connect (mRevertAction, SIGNAL (triggered()), this, SLOT (revertRecord())); + connect (mRevertAction, SIGNAL (triggered()), mDispatcher, SLOT (executeRevert())); addAction (mRevertAction); mDeleteAction = new QAction (tr ("Delete Record"), this); - connect (mDeleteAction, SIGNAL (triggered()), this, SLOT (deleteRecord())); + connect (mDeleteAction, SIGNAL (triggered()), mDispatcher, SLOT (executeDelete())); addAction (mDeleteAction); mMoveUpAction = new QAction (tr ("Move Up"), this); @@ -263,6 +217,18 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, connect (mPreviewAction, SIGNAL (triggered()), this, SLOT (previewRecord())); addAction (mPreviewAction); + /// \todo add a user option, that redirects the extended action to an input panel (in + /// the bottom bar) that lets the user select which record collections should be + /// modified. + + mExtendedDeleteAction = new QAction (tr ("Extended Delete Record"), this); + connect (mExtendedDeleteAction, SIGNAL (triggered()), mDispatcher, SLOT (executeExtendedDelete())); + addAction (mExtendedDeleteAction); + + mExtendedRevertAction = new QAction (tr ("Extended Revert Record"), this); + connect (mExtendedRevertAction, SIGNAL (triggered()), mDispatcher, SLOT (executeExtendedRevert())); + addAction (mExtendedRevertAction); + connect (mProxyModel, SIGNAL (rowsInserted (const QModelIndex&, int, int)), this, SLOT (tableSizeUpdate())); @@ -283,53 +249,19 @@ void CSVWorld::Table::setEditLock (bool locked) (*iter)->setEditLock (locked); DragRecordTable::setEditLock(locked); + mDispatcher->setEditLock (locked); } CSMWorld::UniversalId CSVWorld::Table::getUniversalId (int row) const { - return CSMWorld::UniversalId ( - static_cast (mProxyModel->data (mProxyModel->index (row, 2)).toInt()), - mProxyModel->data (mProxyModel->index (row, 0)).toString().toUtf8().constData()); -} + row = mProxyModel->mapToSource (mProxyModel->index (row, 0)).row(); -void CSVWorld::Table::revertRecord() -{ - if (!mEditLock) - { - std::vector revertableIds = listRevertableSelectedIds(); - - if (!revertableIds.empty()) - { - if (revertableIds.size()>1) - mDocument.getUndoStack().beginMacro (tr ("Revert multiple records")); - - for (std::vector::const_iterator iter (revertableIds.begin()); iter!=revertableIds.end(); ++iter) - mDocument.getUndoStack().push (new CSMWorld::RevertCommand (*mModel, *iter)); - - if (revertableIds.size()>1) - mDocument.getUndoStack().endMacro(); - } - } -} + int idColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Id); + int typeColumn = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_RecordType); -void CSVWorld::Table::deleteRecord() -{ - if (!mEditLock) - { - std::vector deletableIds = listDeletableSelectedIds(); - - if (!deletableIds.empty()) - { - if (deletableIds.size()>1) - mDocument.getUndoStack().beginMacro (tr ("Delete multiple records")); - - for (std::vector::const_iterator iter (deletableIds.begin()); iter!=deletableIds.end(); ++iter) - mDocument.getUndoStack().push (new CSMWorld::DeleteCommand (*mModel, *iter)); - - if (deletableIds.size()>1) - mDocument.getUndoStack().endMacro(); - } - } + return CSMWorld::UniversalId ( + static_cast (mModel->data (mModel->index (row, typeColumn)).toInt()), + mModel->data (mModel->index (row, idColumn)).toString().toUtf8().constData()); } void CSVWorld::Table::editRecord() diff --git a/apps/opencs/view/world/table.hpp b/apps/opencs/view/world/table.hpp index 3b1d40e78..255c430ea 100644 --- a/apps/opencs/view/world/table.hpp +++ b/apps/opencs/view/world/table.hpp @@ -24,6 +24,7 @@ namespace CSMWorld class UniversalId; class IdTableProxyModel; class IdTable; + class CommandDispatcher; } namespace CSVWorld @@ -45,18 +46,17 @@ namespace CSVWorld QAction *mMoveDownAction; QAction *mViewAction; QAction *mPreviewAction; + QAction *mExtendedDeleteAction; + QAction *mExtendedRevertAction; CSMWorld::IdTableProxyModel *mProxyModel; CSMWorld::IdTable *mModel; int mRecordStatusDisplay; + CSMWorld::CommandDispatcher *mDispatcher; private: void contextMenuEvent (QContextMenuEvent *event); - std::vector listRevertableSelectedIds() const; - - std::vector listDeletableSelectedIds() const; - void mouseMoveEvent(QMouseEvent *event); void dropEvent(QDropEvent *event); @@ -93,10 +93,6 @@ namespace CSVWorld private slots: - void revertRecord(); - - void deleteRecord(); - void editRecord(); void cloneRecord(); diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index b76756ca0..7c047adef 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -33,7 +33,7 @@ add_openmw_dir (mwgui merchantrepair repair soulgemdialog companionwindow bookpage journalviewmodel journalbooks keywordsearch itemmodel containeritemmodel inventoryitemmodel sortfilteritemmodel itemview tradeitemmodel companionitemmodel pickpocketitemmodel fontloader controllers savegamedialog - recharge mode videowidget backgroundimage + recharge mode videowidget backgroundimage itemwidget ) add_openmw_dir (mwdialogue @@ -142,10 +142,6 @@ if(APPLE) endif() endif(APPLE) -if(DPKG_PROGRAM) - INSTALL(TARGETS openmw RUNTIME DESTINATION games COMPONENT openmw) -endif(DPKG_PROGRAM) - if (BUILD_WITH_CODE_COVERAGE) add_definitions (--coverage) target_link_libraries(openmw gcov) diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 3098d953e..338eb2a7c 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -4,19 +4,19 @@ #include #include -#include +#include +#include #include "engine.hpp" -#if defined(_WIN32) && !defined(_CONSOLE) #include #include +#if defined(_WIN32) // For OutputDebugString #define WIN32_LEAN_AND_MEAN #include // makes __argc and __argv available on windows #include - #endif @@ -253,58 +253,8 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat return true; } -int main(int argc, char**argv) -{ -#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX || OGRE_PLATFORM == OGRE_PLATFORM_APPLE - // Unix crash catcher - if ((argc == 2 && strcmp(argv[1], "--cc-handle-crash") == 0) || !is_debugger_attached()) - { - int s[5] = { SIGSEGV, SIGILL, SIGFPE, SIGBUS, SIGABRT }; - cc_install_handlers(argc, argv, 5, s, "crash.log", NULL); - std::cout << "Installing crash catcher" << std::endl; - } - else - std::cout << "Running in a debugger, not installing crash catcher" << std::endl; -#endif - -#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE - // set current dir to bundle path - boost::filesystem::path bundlePath = boost::filesystem::path(Ogre::macBundlePath()).parent_path(); - boost::filesystem::current_path(bundlePath); -#endif - - try - { - Files::ConfigurationManager cfgMgr; - OMW::Engine engine(cfgMgr); +#if defined(_WIN32) && defined(_DEBUG) - if (parseOptions(argc, argv, engine, cfgMgr)) - { - engine.go(); - } - } - catch (std::exception &e) - { -#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX || OGRE_PLATFORM == OGRE_PLATFORM_APPLE - if (isatty(fileno(stdin)) || !SDL_WasInit(SDL_INIT_VIDEO)) - std::cerr << "\nERROR: " << e.what() << std::endl; - else -#endif - SDL_ShowSimpleMessageBox(0, "OpenMW: Fatal error", e.what(), NULL); - - return 1; - } - - return 0; -} - -// Platform specific for Windows when there is no console built into the executable. -// Windows will call the WinMain function instead of main in this case, the normal -// main function is then called with the __argc and __argv parameters. -// In addition if it is a debug build it will redirect cout to the debug console in Visual Studio -#if defined(_WIN32) && !defined(_CONSOLE) - -#if defined(_DEBUG) class DebugOutput : public boost::iostreams::sink { public: @@ -318,11 +268,11 @@ public: } }; #else -class Logger : public boost::iostreams::sink +class Tee : public boost::iostreams::sink { public: - Logger(std::ofstream &stream) - : out(stream) + Tee(std::ostream &stream, std::ostream &stream2) + : out(stream), out2(stream2) { } @@ -330,38 +280,98 @@ public: { out.write (str, size); out.flush(); + out2.write (str, size); + out2.flush(); return size; } private: - std::ofstream &out; + std::ostream &out; + std::ostream &out2; }; #endif -int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) +int main(int argc, char**argv) { - std::streambuf* old_rdbuf = std::cout.rdbuf (); + std::streambuf* cout_rdbuf = std::cout.rdbuf (); + std::streambuf* cerr_rdbuf = std::cerr.rdbuf (); int ret = 0; -#if defined(_DEBUG) - // Redirect cout to VS debug output when running in debug mode + try { + Files::ConfigurationManager cfgMgr; + +#if defined(_WIN32) && defined(_DEBUG) + // Redirect cout and cerr to VS debug output when running in debug mode boost::iostreams::stream_buffer sb; sb.open(DebugOutput()); + std::cout.rdbuf (&sb); + std::cerr.rdbuf (&sb); #else - // Redirect cout to openmw.log - std::ofstream logfile ("openmw.log"); - { - boost::iostreams::stream_buffer sb; - sb.open (Logger (logfile)); + // Redirect cout and cerr to openmw.log + std::ofstream logfile (std::string(cfgMgr.getLogPath().string() + "/openmw.log").c_str()); + + boost::iostreams::stream_buffer coutsb; + std::ostream oldcout(cout_rdbuf); + coutsb.open (Tee(logfile, oldcout)); + std::cout.rdbuf (&coutsb); + + boost::iostreams::stream_buffer cerrsb; + std::ostream oldcerr(cerr_rdbuf); + cerrsb.open (Tee(logfile, oldcerr)); + std::cerr.rdbuf (&cerrsb); +#endif + +#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX || OGRE_PLATFORM == OGRE_PLATFORM_APPLE + // Unix crash catcher + if ((argc == 2 && strcmp(argv[1], "--cc-handle-crash") == 0) || !is_debugger_attached()) + { + int s[5] = { SIGSEGV, SIGILL, SIGFPE, SIGBUS, SIGABRT }; + cc_install_handlers(argc, argv, 5, s, std::string(cfgMgr.getLogPath().string() + "/crash.log").c_str(), NULL); + std::cout << "Installing crash catcher" << std::endl; + } + else + std::cout << "Running in a debugger, not installing crash catcher" << std::endl; +#endif + +#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE + // set current dir to bundle path + boost::filesystem::path bundlePath = boost::filesystem::path(Ogre::macBundlePath()).parent_path(); + boost::filesystem::current_path(bundlePath); #endif - std::cout.rdbuf (&sb); - ret = main (__argc, __argv); + OMW::Engine engine(cfgMgr); - std::cout.rdbuf(old_rdbuf); + if (parseOptions(argc, argv, engine, cfgMgr)) + { + engine.go(); + } + } + catch (std::exception &e) + { +#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX || OGRE_PLATFORM == OGRE_PLATFORM_APPLE + if (isatty(fileno(stdin))) + std::cerr << "\nERROR: " << e.what() << std::endl; + else +#endif + SDL_ShowSimpleMessageBox(0, "OpenMW: Fatal error", e.what(), NULL); + + ret = 1; } + + // Restore cout and cerr + std::cout.rdbuf(cout_rdbuf); + std::cerr.rdbuf(cerr_rdbuf); + return ret; } +// Platform specific for Windows when there is no console built into the executable. +// Windows will call the WinMain function instead of main in this case, the normal +// main function is then called with the __argc and __argv parameters. +#if defined(_WIN32) && !defined(_CONSOLE) +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) +{ + return main(__argc, __argv); +} #endif diff --git a/apps/openmw/mwbase/dialoguemanager.hpp b/apps/openmw/mwbase/dialoguemanager.hpp index dfb002cfc..d0e64b23c 100644 --- a/apps/openmw/mwbase/dialoguemanager.hpp +++ b/apps/openmw/mwbase/dialoguemanager.hpp @@ -76,6 +76,9 @@ namespace MWBase /// @return faction1's opinion of faction2 virtual int getFactionReaction (const std::string& faction1, const std::string& faction2) const = 0; + + /// Removes the last added topic response for the given actor from the journal + virtual void clearInfoActor (const MWWorld::Ptr& actor) const = 0; }; } diff --git a/apps/openmw/mwbase/journal.hpp b/apps/openmw/mwbase/journal.hpp index a49ebb9bc..938cec74b 100644 --- a/apps/openmw/mwbase/journal.hpp +++ b/apps/openmw/mwbase/journal.hpp @@ -59,7 +59,12 @@ namespace MWBase virtual int getJournalIndex (const std::string& id) const = 0; ///< Get the journal index. - virtual void addTopic (const std::string& topicId, const std::string& infoId, const std::string& actorName) = 0; + virtual void addTopic (const std::string& topicId, const std::string& infoId, const MWWorld::Ptr& actor) = 0; + /// \note topicId must be lowercase + + virtual void removeLastAddedTopicResponse (const std::string& topicId, const std::string& actorName) = 0; + ///< Removes the last topic response added for the given topicId and actor name. + /// \note topicId must be lowercase virtual TEntryIter begin() const = 0; ///< Iterator pointing to the begin of the main journal. diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 2dfa50eb5..0f45542ba 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -328,6 +328,8 @@ namespace MWBase /** Used when one Modal adds another Modal \param input Pointer to the current modal, to ensure proper modal is removed **/ virtual void removeCurrentModal(MWGui::WindowModal* input) = 0; + + virtual void pinWindow (MWGui::GuiWindow window) = 0; }; } diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 1a6e4e321..5744ebb9b 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -345,7 +345,9 @@ namespace MWClass getCreatureStats(ptr).setAttacked(true); // Self defense - if (!attacker.isEmpty() && ptr.getClass().getCreatureStats(ptr).getAiSetting(MWMechanics::CreatureStats::AI_Fight).getModified() < 80) + if (!attacker.isEmpty() && ptr.getClass().getCreatureStats(ptr).getAiSetting(MWMechanics::CreatureStats::AI_Fight).getModified() < 80 + && (canWalk(ptr) || canFly(ptr) || canSwim(ptr))) // No retaliation for totally static creatures + // (they have no movement or attacks anyway) MWBase::Environment::get().getMechanicsManager()->startCombat(ptr, attacker); if(!successful) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 7405292b4..5ec192ab2 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -680,7 +680,7 @@ namespace MWClass else getCreatureStats(ptr).setHitRecovery(true); // Is this supposed to always occur? - if(ishealth) + if(ishealth && !attacker.isEmpty()) // Don't use armor mitigation for fall damage { // Hit percentages: // cuirass = 30% diff --git a/apps/openmw/mwclass/static.cpp b/apps/openmw/mwclass/static.cpp index 4ac41350f..8768bde06 100644 --- a/apps/openmw/mwclass/static.cpp +++ b/apps/openmw/mwclass/static.cpp @@ -14,9 +14,12 @@ namespace MWClass { void Static::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { + MWWorld::LiveCellRef *ref = + ptr.get(); + const std::string model = getModel(ptr); if (!model.empty()) { - renderingInterface.getObjects().insertModel(ptr, model); + renderingInterface.getObjects().insertModel(ptr, model, !ref->mBase->mPersistent); } } diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 57681b7fb..aa7df1fd4 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -140,7 +140,11 @@ namespace MWDialogue mActorKnownTopics.clear(); MWGui::DialogueWindow* win = MWBase::Environment::get().getWindowManager()->getDialogueWindow(); - win->startDialogue(actor, actor.getClass().getName (actor)); + + // If the dialogue window was already open, keep the existing history + bool resetHistory = (!MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_Dialogue)); + + win->startDialogue(actor, actor.getClass().getName (actor), resetHistory); //setup the list of topics known by the actor. Topics who are also on the knownTopics list will be added to the GUI updateTopics(); @@ -294,7 +298,7 @@ namespace MWDialogue { if (iter->mId == info->mId) { - MWBase::Environment::get().getJournal()->addTopic (topic, info->mId, mActor.getClass().getName(mActor)); + MWBase::Environment::get().getJournal()->addTopic (topic, info->mId, mActor); break; } } @@ -472,7 +476,7 @@ namespace MWDialogue { if (iter->mId == info->mId) { - MWBase::Environment::get().getJournal()->addTopic (mLastTopic, info->mId, mActor.getClass().getName(mActor)); + MWBase::Environment::get().getJournal()->addTopic (mLastTopic, info->mId, mActor); break; } } @@ -694,6 +698,15 @@ namespace MWDialogue return diff; } + void DialogueManager::clearInfoActor(const MWWorld::Ptr &actor) const + { + if (actor == mActor && !mLastTopic.empty()) + { + MWBase::Environment::get().getJournal()->removeLastAddedTopicResponse( + mLastTopic, actor.getClass().getName(actor)); + } + } + std::vector ParseHyperText(const std::string& text) { std::vector result; diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp index 94f8f3ec1..6553ddc01 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp @@ -40,7 +40,7 @@ namespace MWDialogue bool mTalkedTo; int mChoice; - std::string mLastTopic; + std::string mLastTopic; // last topic ID, lowercase bool mIsInChoice; float mTemporaryDispositionChange; @@ -99,6 +99,9 @@ namespace MWDialogue /// @return faction1's opinion of faction2 virtual int getFactionReaction (const std::string& faction1, const std::string& faction2) const; + + /// Removes the last added topic response for the given actor from the journal + virtual void clearInfoActor (const MWWorld::Ptr& actor) const; }; diff --git a/apps/openmw/mwdialogue/journalentry.cpp b/apps/openmw/mwdialogue/journalentry.cpp index 55574bf3e..b92d7cace 100644 --- a/apps/openmw/mwdialogue/journalentry.cpp +++ b/apps/openmw/mwdialogue/journalentry.cpp @@ -5,16 +5,21 @@ #include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwscript/interpretercontext.hpp" + + namespace MWDialogue { Entry::Entry() {} - Entry::Entry (const std::string& topic, const std::string& infoId) + Entry::Entry (const std::string& topic, const std::string& infoId, const MWWorld::Ptr& actor) : mInfoId (infoId) { const ESM::Dialogue *dialogue = @@ -24,8 +29,17 @@ namespace MWDialogue iter!=dialogue->mInfo.end(); ++iter) if (iter->mId == mInfoId) { - /// \todo text replacement - mText = iter->mResponse; + if (actor.isEmpty()) + { + MWScript::InterpreterContext interpreterContext(NULL,MWWorld::Ptr()); + mText = Interpreter::fixDefinesDialog(iter->mResponse, interpreterContext); + } + else + { + MWScript::InterpreterContext interpreterContext(&actor.getRefData().getLocals(),actor); + mText = Interpreter::fixDefinesDialog(iter->mResponse, interpreterContext); + } + return; } @@ -49,8 +63,8 @@ namespace MWDialogue JournalEntry::JournalEntry() {} - JournalEntry::JournalEntry (const std::string& topic, const std::string& infoId) - : Entry (topic, infoId), mTopic (topic) + JournalEntry::JournalEntry (const std::string& topic, const std::string& infoId, const MWWorld::Ptr& actor) + : Entry (topic, infoId, actor), mTopic (topic) {} JournalEntry::JournalEntry (const ESM::JournalEntry& record) @@ -65,7 +79,7 @@ namespace MWDialogue JournalEntry JournalEntry::makeFromQuest (const std::string& topic, int index) { - return JournalEntry (topic, idFromIndex (topic, index)); + return JournalEntry (topic, idFromIndex (topic, index), MWWorld::Ptr()); } std::string JournalEntry::idFromIndex (const std::string& topic, int index) @@ -90,7 +104,7 @@ namespace MWDialogue StampedJournalEntry::StampedJournalEntry (const std::string& topic, const std::string& infoId, int day, int month, int dayOfMonth) - : JournalEntry (topic, infoId), mDay (day), mMonth (month), mDayOfMonth (dayOfMonth) + : JournalEntry (topic, infoId, MWWorld::Ptr()), mDay (day), mMonth (month), mDayOfMonth (dayOfMonth) {} StampedJournalEntry::StampedJournalEntry (const ESM::JournalEntry& record) diff --git a/apps/openmw/mwdialogue/journalentry.hpp b/apps/openmw/mwdialogue/journalentry.hpp index a77ba4f7c..3ae3efcc8 100644 --- a/apps/openmw/mwdialogue/journalentry.hpp +++ b/apps/openmw/mwdialogue/journalentry.hpp @@ -8,6 +8,11 @@ namespace ESM struct JournalEntry; } +namespace MWWorld +{ + class Ptr; +} + namespace MWDialogue { /// \brief Basic quest/dialogue/topic entry @@ -19,7 +24,8 @@ namespace MWDialogue Entry(); - Entry (const std::string& topic, const std::string& infoId); + /// actor is optional + Entry (const std::string& topic, const std::string& infoId, const MWWorld::Ptr& actor); Entry (const ESM::JournalEntry& record); @@ -37,7 +43,7 @@ namespace MWDialogue JournalEntry(); - JournalEntry (const std::string& topic, const std::string& infoId); + JournalEntry (const std::string& topic, const std::string& infoId, const MWWorld::Ptr& actor); JournalEntry (const ESM::JournalEntry& record); diff --git a/apps/openmw/mwdialogue/journalimp.cpp b/apps/openmw/mwdialogue/journalimp.cpp index 04aa0534d..9497347e3 100644 --- a/apps/openmw/mwdialogue/journalimp.cpp +++ b/apps/openmw/mwdialogue/journalimp.cpp @@ -9,6 +9,7 @@ #include #include "../mwworld/esmstore.hpp" +#include "../mwworld/class.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -103,15 +104,25 @@ namespace MWDialogue quest.setIndex (index); } - void Journal::addTopic (const std::string& topicId, const std::string& infoId, const std::string& actorName) + void Journal::addTopic (const std::string& topicId, const std::string& infoId, const MWWorld::Ptr& actor) { Topic& topic = getTopic (topicId); - JournalEntry entry(topicId, infoId); - entry.mActorName = actorName; + JournalEntry entry(topicId, infoId, actor); + entry.mActorName = actor.getClass().getName(actor); topic.addEntry (entry); } + void Journal::removeLastAddedTopicResponse(const std::string &topicId, const std::string &actorName) + { + Topic& topic = getTopic (topicId); + + topic.removeLastAddedResponse(actorName); + + if (topic.begin() == topic.end()) + mTopics.erase(mTopics.find(topicId)); // All responses removed -> remove topic + } + int Journal::getJournalIndex (const std::string& id) const { TQuestContainer::const_iterator iter = mQuests.find (id); diff --git a/apps/openmw/mwdialogue/journalimp.hpp b/apps/openmw/mwdialogue/journalimp.hpp index 00511f47c..d15b909fa 100644 --- a/apps/openmw/mwdialogue/journalimp.hpp +++ b/apps/openmw/mwdialogue/journalimp.hpp @@ -38,7 +38,12 @@ namespace MWDialogue virtual int getJournalIndex (const std::string& id) const; ///< Get the journal index. - virtual void addTopic (const std::string& topicId, const std::string& infoId, const std::string& actorName); + virtual void addTopic (const std::string& topicId, const std::string& infoId, const MWWorld::Ptr& actor); + /// \note topicId must be lowercase + + virtual void removeLastAddedTopicResponse (const std::string& topicId, const std::string& actorName); + ///< Removes the last topic response added for the given topicId and actor name. + /// \note topicId must be lowercase virtual TEntryIter begin() const; ///< Iterator pointing to the begin of the main journal. diff --git a/apps/openmw/mwdialogue/topic.cpp b/apps/openmw/mwdialogue/topic.cpp index f7df305c7..c1a45f841 100644 --- a/apps/openmw/mwdialogue/topic.cpp +++ b/apps/openmw/mwdialogue/topic.cpp @@ -59,8 +59,16 @@ namespace MWDialogue return mEntries.end(); } - JournalEntry Topic::getEntry (const std::string& infoId) const + void Topic::removeLastAddedResponse (const std::string& actorName) { - return JournalEntry (mTopic, infoId); + for (std::vector::reverse_iterator it = mEntries.rbegin(); + it != mEntries.rend(); ++it) + { + if (it->mActorName == actorName) + { + mEntries.erase( (++it).base() ); // erase doesn't take a reverse_iterator + return; + } + } } } diff --git a/apps/openmw/mwdialogue/topic.hpp b/apps/openmw/mwdialogue/topic.hpp index 02fa6d524..72486ef8a 100644 --- a/apps/openmw/mwdialogue/topic.hpp +++ b/apps/openmw/mwdialogue/topic.hpp @@ -48,13 +48,13 @@ namespace MWDialogue virtual std::string getName() const; + void removeLastAddedResponse (const std::string& actorName); + TEntryIter begin() const; ///< Iterator pointing to the begin of the journal for this topic. TEntryIter end() const; ///< Iterator pointing past the end of the journal for this topic. - - JournalEntry getEntry (const std::string& infoId) const; }; } diff --git a/apps/openmw/mwgui/alchemywindow.cpp b/apps/openmw/mwgui/alchemywindow.cpp index ab04189a6..0b3e3c168 100644 --- a/apps/openmw/mwgui/alchemywindow.cpp +++ b/apps/openmw/mwgui/alchemywindow.cpp @@ -13,20 +13,7 @@ #include "inventoryitemmodel.hpp" #include "sortfilteritemmodel.hpp" #include "itemview.hpp" - -namespace -{ - std::string getIconPath(MWWorld::Ptr ptr) - { - std::string path = std::string("icons\\"); - path += ptr.getClass().getInventoryIcon(ptr); - int pos = path.rfind("."); - path.erase(pos); - path.append(".dds"); - return path; - } - -} +#include "itemwidget.hpp" namespace MWGui { @@ -149,7 +136,7 @@ namespace MWGui { mApparatus.at (index)->setUserString ("ToolTipType", "ItemPtr"); mApparatus.at (index)->setUserData (*iter); - mApparatus.at (index)->setImageTexture (getIconPath (*iter)); + mApparatus.at (index)->setItem(*iter); } } @@ -189,7 +176,7 @@ namespace MWGui MWMechanics::Alchemy::TIngredientsIterator it = mAlchemy.beginIngredients (); for (int i=0; i<4; ++i) { - MyGUI::ImageBox* ingredient = mIngredients[i]; + ItemWidget* ingredient = mIngredients[i]; MWWorld::Ptr item; if (it != mAlchemy.endIngredients ()) @@ -204,15 +191,15 @@ namespace MWGui if (ingredient->getChildCount()) MyGUI::Gui::getInstance().destroyWidget(ingredient->getChildAt(0)); - ingredient->setImageTexture(""); ingredient->clearUserStrings (); + ingredient->setItem(item); + if (item.isEmpty ()) continue; ingredient->setUserString("ToolTipType", "ItemPtr"); ingredient->setUserData(item); - ingredient->setImageTexture(getIconPath(item)); MyGUI::TextBox* text = ingredient->createWidget("SandBrightText", MyGUI::IntCoord(0, 14, 32, 18), MyGUI::Align::Default, std::string("Label")); text->setTextAlign(MyGUI::Align::Right); diff --git a/apps/openmw/mwgui/alchemywindow.hpp b/apps/openmw/mwgui/alchemywindow.hpp index 4fd4b825c..b538a1f80 100644 --- a/apps/openmw/mwgui/alchemywindow.hpp +++ b/apps/openmw/mwgui/alchemywindow.hpp @@ -11,6 +11,7 @@ namespace MWGui { class ItemView; + class ItemWidget; class SortFilterItemModel; class AlchemyWindow : public WindowBase @@ -44,8 +45,8 @@ namespace MWGui MWMechanics::Alchemy mAlchemy; - std::vector mApparatus; - std::vector mIngredients; + std::vector mApparatus; + std::vector mIngredients; }; } diff --git a/apps/openmw/mwgui/confirmationdialog.cpp b/apps/openmw/mwgui/confirmationdialog.cpp index 89f477598..57c88bfa2 100644 --- a/apps/openmw/mwgui/confirmationdialog.cpp +++ b/apps/openmw/mwgui/confirmationdialog.cpp @@ -30,9 +30,9 @@ namespace MWGui void ConfirmationDialog::exit() { - eventCancelClicked(); - setVisible(false); + + eventCancelClicked(); } void ConfirmationDialog::onCancelButtonClicked(MyGUI::Widget* _sender) @@ -42,8 +42,8 @@ namespace MWGui void ConfirmationDialog::onOkButtonClicked(MyGUI::Widget* _sender) { - eventOkClicked(); - setVisible(false); + + eventOkClicked(); } } diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index 609dea385..4ba454a1c 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -20,6 +20,7 @@ #include "inventorywindow.hpp" #include "itemview.hpp" +#include "itemwidget.hpp" #include "inventoryitemmodel.hpp" #include "sortfilteritemmodel.hpp" #include "pickpocketitemmodel.hpp" @@ -46,22 +47,15 @@ namespace MWGui mSourceSortModel->addDragItem(mItem.mBase, count); } - std::string path = std::string("icons\\"); - path += mItem.mBase.getClass().getInventoryIcon(mItem.mBase); - MyGUI::ImageBox* baseWidget = mDragAndDropWidget->createWidget - ("ImageBox", MyGUI::IntCoord(0, 0, 42, 42), MyGUI::Align::Default); + ItemWidget* baseWidget = mDragAndDropWidget->createWidget + ("MW_ItemIcon", MyGUI::IntCoord(0, 0, 42, 42), MyGUI::Align::Default); mDraggedWidget = baseWidget; - MyGUI::ImageBox* image = baseWidget->createWidget("ImageBox", - MyGUI::IntCoord(5, 5, 32, 32), MyGUI::Align::Default); - size_t pos = path.rfind("."); - if (pos != std::string::npos) - path.erase(pos); - path.append(".dds"); - image->setImageTexture(path); - image->setNeedMouseFocus(false); + baseWidget->setItem(mItem.mBase); + baseWidget->setNeedMouseFocus(false); // text widget that shows item count - MyGUI::TextBox* text = image->createWidget("SandBrightText", + // TODO: move to ItemWidget + MyGUI::TextBox* text = baseWidget->createWidget("SandBrightText", MyGUI::IntCoord(0, 14, 32, 18), MyGUI::Align::Default, std::string("Label")); text->setTextAlign(MyGUI::Align::Right); text->setNeedMouseFocus(false); diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index 441e31e32..e7dd74eee 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -366,10 +366,11 @@ namespace MWGui } } - void DialogueWindow::startDialogue(MWWorld::Ptr actor, std::string npcName) + void DialogueWindow::startDialogue(MWWorld::Ptr actor, std::string npcName, bool resetHistory) { mGoodbye = false; mEnabled = true; + bool sameActor = (mPtr == actor); mPtr = actor; mTopicsList->setEnabled(true); setTitle(npcName); @@ -378,9 +379,12 @@ namespace MWGui mTopicsList->clear(); - for (std::vector::iterator it = mHistoryContents.begin(); it != mHistoryContents.end(); ++it) - delete (*it); - mHistoryContents.clear(); + if (resetHistory || !sameActor) + { + for (std::vector::iterator it = mHistoryContents.begin(); it != mHistoryContents.end(); ++it) + delete (*it); + mHistoryContents.clear(); + } for (std::vector::iterator it = mLinks.begin(); it != mLinks.end(); ++it) delete (*it); diff --git a/apps/openmw/mwgui/dialogue.hpp b/apps/openmw/mwgui/dialogue.hpp index fcb1338b5..4e0ca5dde 100644 --- a/apps/openmw/mwgui/dialogue.hpp +++ b/apps/openmw/mwgui/dialogue.hpp @@ -111,7 +111,7 @@ namespace MWGui void notifyLinkClicked (TypesetBook::InteractiveId link); - void startDialogue(MWWorld::Ptr actor, std::string npcName); + void startDialogue(MWWorld::Ptr actor, std::string npcName, bool resetHistory); void setKeywords(std::list keyWord); void addResponse (const std::string& text, const std::string& title=""); diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp index ccb1fab68..92221977b 100644 --- a/apps/openmw/mwgui/enchantingdialog.cpp +++ b/apps/openmw/mwgui/enchantingdialog.cpp @@ -14,6 +14,7 @@ #include "itemselection.hpp" #include "container.hpp" +#include "itemwidget.hpp" #include "sortfilteritemmodel.hpp" @@ -57,8 +58,45 @@ namespace MWGui void EnchantingDialog::open() { center(); - onRemoveItem(NULL); - onRemoveSoul(NULL); + + setSoulGem(MWWorld::Ptr()); + setItem(MWWorld::Ptr()); + } + + void EnchantingDialog::setSoulGem(const MWWorld::Ptr &gem) + { + if (gem.isEmpty()) + { + mSoulBox->setItem(MWWorld::Ptr()); + mSoulBox->clearUserStrings(); + mEnchanting.setSoulGem(MWWorld::Ptr()); + } + else + { + mSoulBox->setItem(gem); + mSoulBox->setUserString ("ToolTipType", "ItemPtr"); + mSoulBox->setUserData(gem); + mEnchanting.setSoulGem(gem); + } + updateLabels(); + } + + void EnchantingDialog::setItem(const MWWorld::Ptr &item) + { + if (item.isEmpty()) + { + mItemBox->setItem(MWWorld::Ptr()); + mItemBox->clearUserStrings(); + mEnchanting.setOldItem(MWWorld::Ptr()); + } + else + { + mItemBox->setItem(item); + mItemBox->setUserString ("ToolTipType", "ItemPtr"); + mItemBox->setUserData(item); + mEnchanting.setOldItem(item); + } + updateLabels(); } void EnchantingDialog::exit() @@ -122,16 +160,7 @@ namespace MWGui startEditing(); mEnchanting.setSoulGem(soulgem); - MyGUI::ImageBox* image = mSoulBox->createWidget("ImageBox", MyGUI::IntCoord(0, 0, 32, 32), MyGUI::Align::Default); - std::string path = std::string("icons\\"); - path += soulgem.getClass().getInventoryIcon(soulgem); - int pos = path.rfind("."); - path.erase(pos); - path.append(".dds"); - image->setImageTexture (path); - image->setUserString ("ToolTipType", "ItemPtr"); - image->setUserData(soulgem); - image->eventMouseButtonClick += MyGUI::newDelegate(this, &EnchantingDialog::onRemoveSoul); + setSoulGem(soulgem); mPrice->setVisible(false); mPriceText->setVisible(false); @@ -151,46 +180,31 @@ namespace MWGui void EnchantingDialog::onSelectItem(MyGUI::Widget *sender) { - delete mItemSelectionDialog; - mItemSelectionDialog = new ItemSelectionDialog("#{sEnchantItems}"); - mItemSelectionDialog->eventItemSelected += MyGUI::newDelegate(this, &EnchantingDialog::onItemSelected); - mItemSelectionDialog->eventDialogCanceled += MyGUI::newDelegate(this, &EnchantingDialog::onItemCancel); - mItemSelectionDialog->setVisible(true); - mItemSelectionDialog->openContainer(MWBase::Environment::get().getWorld()->getPlayerPtr()); - mItemSelectionDialog->setFilter(SortFilterItemModel::Filter_OnlyEnchantable); + if (mEnchanting.getOldItem().isEmpty()) + { + delete mItemSelectionDialog; + mItemSelectionDialog = new ItemSelectionDialog("#{sEnchantItems}"); + mItemSelectionDialog->eventItemSelected += MyGUI::newDelegate(this, &EnchantingDialog::onItemSelected); + mItemSelectionDialog->eventDialogCanceled += MyGUI::newDelegate(this, &EnchantingDialog::onItemCancel); + mItemSelectionDialog->setVisible(true); + mItemSelectionDialog->openContainer(MWBase::Environment::get().getWorld()->getPlayerPtr()); + mItemSelectionDialog->setFilter(SortFilterItemModel::Filter_OnlyEnchantable); + } + else + { + setItem(MWWorld::Ptr()); + } } void EnchantingDialog::onItemSelected(MWWorld::Ptr item) { mItemSelectionDialog->setVisible(false); - while (mItemBox->getChildCount ()) - MyGUI::Gui::getInstance ().destroyWidget (mItemBox->getChildAt(0)); - - MyGUI::ImageBox* image = mItemBox->createWidget("ImageBox", MyGUI::IntCoord(0, 0, 32, 32), MyGUI::Align::Default); - std::string path = std::string("icons\\"); - path += item.getClass().getInventoryIcon(item); - int pos = path.rfind("."); - path.erase(pos); - path.append(".dds"); - image->setImageTexture (path); - image->setUserString ("ToolTipType", "ItemPtr"); - image->setUserData(item); - image->eventMouseButtonClick += MyGUI::newDelegate(this, &EnchantingDialog::onRemoveItem); - - mEnchanting.setOldItem(item); + setItem(item); mEnchanting.nextCastStyle(); updateLabels(); } - void EnchantingDialog::onRemoveItem(MyGUI::Widget *sender) - { - while (mItemBox->getChildCount ()) - MyGUI::Gui::getInstance ().destroyWidget (mItemBox->getChildAt(0)); - mEnchanting.setOldItem(MWWorld::Ptr()); - updateLabels(); - } - void EnchantingDialog::onItemCancel() { mItemSelectionDialog->setVisible(false); @@ -207,28 +221,7 @@ namespace MWGui return; } - while (mSoulBox->getChildCount ()) - MyGUI::Gui::getInstance ().destroyWidget (mSoulBox->getChildAt(0)); - - MyGUI::ImageBox* image = mSoulBox->createWidget("ImageBox", MyGUI::IntCoord(0, 0, 32, 32), MyGUI::Align::Default); - std::string path = std::string("icons\\"); - path += item.getClass().getInventoryIcon(item); - int pos = path.rfind("."); - path.erase(pos); - path.append(".dds"); - image->setImageTexture (path); - image->setUserString ("ToolTipType", "ItemPtr"); - image->setUserData(item); - image->eventMouseButtonClick += MyGUI::newDelegate(this, &EnchantingDialog::onRemoveSoul); - updateLabels(); - } - - void EnchantingDialog::onRemoveSoul(MyGUI::Widget *sender) - { - while (mSoulBox->getChildCount ()) - MyGUI::Gui::getInstance ().destroyWidget (mSoulBox->getChildAt(0)); - mEnchanting.setSoulGem(MWWorld::Ptr()); - updateLabels(); + setSoulGem(item); } void EnchantingDialog::onSoulCancel() @@ -238,15 +231,22 @@ namespace MWGui void EnchantingDialog::onSelectSoul(MyGUI::Widget *sender) { - delete mItemSelectionDialog; - mItemSelectionDialog = new ItemSelectionDialog("#{sSoulGemsWithSouls}"); - mItemSelectionDialog->eventItemSelected += MyGUI::newDelegate(this, &EnchantingDialog::onSoulSelected); - mItemSelectionDialog->eventDialogCanceled += MyGUI::newDelegate(this, &EnchantingDialog::onSoulCancel); - mItemSelectionDialog->setVisible(true); - mItemSelectionDialog->openContainer(MWBase::Environment::get().getWorld()->getPlayerPtr()); - mItemSelectionDialog->setFilter(SortFilterItemModel::Filter_OnlyChargedSoulstones); - - //MWBase::Environment::get().getWindowManager()->messageBox("#{sInventorySelectNoSoul}"); + if (mEnchanting.getGem().isEmpty()) + { + delete mItemSelectionDialog; + mItemSelectionDialog = new ItemSelectionDialog("#{sSoulGemsWithSouls}"); + mItemSelectionDialog->eventItemSelected += MyGUI::newDelegate(this, &EnchantingDialog::onSoulSelected); + mItemSelectionDialog->eventDialogCanceled += MyGUI::newDelegate(this, &EnchantingDialog::onSoulCancel); + mItemSelectionDialog->setVisible(true); + mItemSelectionDialog->openContainer(MWBase::Environment::get().getWorld()->getPlayerPtr()); + mItemSelectionDialog->setFilter(SortFilterItemModel::Filter_OnlyChargedSoulstones); + + //MWBase::Environment::get().getWindowManager()->messageBox("#{sInventorySelectNoSoul}"); + } + else + { + setSoulGem(MWWorld::Ptr()); + } } void EnchantingDialog::notifyEffectsChanged () diff --git a/apps/openmw/mwgui/enchantingdialog.hpp b/apps/openmw/mwgui/enchantingdialog.hpp index 7e641702e..b75ae8280 100644 --- a/apps/openmw/mwgui/enchantingdialog.hpp +++ b/apps/openmw/mwgui/enchantingdialog.hpp @@ -11,6 +11,7 @@ namespace MWGui { class ItemSelectionDialog; + class ItemWidget; class EnchantingDialog : public WindowBase, public ReferenceInterface, public EffectEditorBase { @@ -22,6 +23,9 @@ namespace MWGui virtual void exit(); + void setSoulGem (const MWWorld::Ptr& gem); + void setItem (const MWWorld::Ptr& item); + void startEnchanting(MWWorld::Ptr actor); void startSelfEnchanting(MWWorld::Ptr soulgem); @@ -32,8 +36,6 @@ namespace MWGui void onCancelButtonClicked(MyGUI::Widget* sender); void onSelectItem (MyGUI::Widget* sender); void onSelectSoul (MyGUI::Widget* sender); - void onRemoveItem (MyGUI::Widget* sender); - void onRemoveSoul (MyGUI::Widget* sender); void onItemSelected(MWWorld::Ptr item); void onItemCancel(); @@ -46,8 +48,8 @@ namespace MWGui ItemSelectionDialog* mItemSelectionDialog; MyGUI::Button* mCancelButton; - MyGUI::ImageBox* mItemBox; - MyGUI::ImageBox* mSoulBox; + ItemWidget* mItemBox; + ItemWidget* mSoulBox; MyGUI::Button* mTypeButton; MyGUI::Button* mBuyButton; diff --git a/apps/openmw/mwgui/fontloader.cpp b/apps/openmw/mwgui/fontloader.cpp index 59c2e7ca6..b7c2007b4 100644 --- a/apps/openmw/mwgui/fontloader.cpp +++ b/apps/openmw/mwgui/fontloader.cpp @@ -64,10 +64,15 @@ namespace return unicode; } + // getUtf8, aka the worst function ever written. + // This includes various hacks for dealing with Morrowind's .fnt files that are *mostly* + // in the expected win12XX encoding, but also have randomly swapped characters sometimes. + // Looks like the Morrowind developers found standard encodings too boring and threw in some twists for fun. std::string getUtf8 (unsigned char c, ToUTF8::Utf8Encoder& encoder, ToUTF8::FromType encoding) { if (encoding == ToUTF8::WINDOWS_1250) { + // Hacks for polish font unsigned char win1250; std::map conv; conv[0x80] = 0xc6; @@ -101,7 +106,8 @@ namespace conv[0xa3] = 0xbf; conv[0xa4] = 0x0; // not contained in win1250 conv[0xe1] = 0x8c; - conv[0xe1] = 0x8c; + // Can't remember if this was supposed to read 0xe2, or is it just an extraneous copypaste? + //conv[0xe1] = 0x8c; conv[0xe3] = 0x0; // not contained in win1250 conv[0xf5] = 0x0; // not contained in win1250 @@ -252,6 +258,25 @@ namespace MWGui code->addAttribute("bearing", MyGUI::utility::toString(data[i].kerning) + " " + MyGUI::utility::toString((fontSize-data[i].ascent))); + // More hacks! The french game uses several win1252 characters that are not included + // in the cp437 encoding of the font. Fall back to similar available characters. + // Same for U+2013 + std::map additional; + additional[39] = 0x2019; // apostrophe + additional[45] = 0x2013; // dash + if (additional.find(i) != additional.end() && mEncoding == ToUTF8::CP437) + { + MyGUI::xml::ElementPtr code = codes->createChild("Code"); + code->addAttribute("index", additional[i]); + code->addAttribute("coord", MyGUI::utility::toString(x1) + " " + + MyGUI::utility::toString(y1) + " " + + MyGUI::utility::toString(w) + " " + + MyGUI::utility::toString(h)); + code->addAttribute("advance", data[i].width); + code->addAttribute("bearing", MyGUI::utility::toString(data[i].kerning) + " " + + MyGUI::utility::toString((fontSize-data[i].ascent))); + } + // ASCII vertical bar, use this as text input cursor if (i == 124) { @@ -265,18 +290,30 @@ namespace MWGui cursorCode->addAttribute("bearing", MyGUI::utility::toString(data[i].kerning) + " " + MyGUI::utility::toString((fontSize-data[i].ascent))); } + + // Question mark, use for NotDefined marker (used for glyphs not existing in the font) + if (i == 63) + { + MyGUI::xml::ElementPtr cursorCode = codes->createChild("Code"); + cursorCode->addAttribute("index", MyGUI::FontCodeType::NotDefined); + cursorCode->addAttribute("coord", MyGUI::utility::toString(x1) + " " + + MyGUI::utility::toString(y1) + " " + + MyGUI::utility::toString(w) + " " + + MyGUI::utility::toString(h)); + cursorCode->addAttribute("advance", data[i].width); + cursorCode->addAttribute("bearing", MyGUI::utility::toString(data[i].kerning) + " " + + MyGUI::utility::toString((fontSize-data[i].ascent))); + } } // These are required as well, but the fonts don't provide them - for (int i=0; i<3; ++i) + for (int i=0; i<2; ++i) { MyGUI::FontCodeType::Enum type; if(i == 0) type = MyGUI::FontCodeType::Selected; else if (i == 1) type = MyGUI::FontCodeType::SelectedBack; - else if (i == 2) - type = MyGUI::FontCodeType::NotDefined; MyGUI::xml::ElementPtr cursorCode = codes->createChild("Code"); cursorCode->addAttribute("index", type); diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index ede5750a5..d87ac7ec5 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -18,6 +18,7 @@ #include "container.hpp" #include "itemmodel.hpp" +#include "itemwidget.hpp" namespace MWGui { @@ -423,9 +424,6 @@ namespace MWGui mSpellStatus->setProgressRange(100); mSpellStatus->setProgressPosition(successChancePercent); - if (mSpellImage->getChildCount()) - MyGUI::Gui::getInstance().destroyWidget(mSpellImage->getChildAt(0)); - mSpellBox->setUserString("ToolTipType", "Spell"); mSpellBox->setUserString("Spell", spellId); @@ -438,7 +436,9 @@ namespace MWGui icon.insert(slashPos+1, "b_"); icon = std::string("icons\\") + icon; Widgets::fixTexturePath(icon); - mSpellImage->setImageTexture(icon); + + mSpellImage->setItem(MWWorld::Ptr()); + mSpellImage->setIcon(icon); } void HUD::setSelectedEnchantItem(const MWWorld::Ptr& item, int chargePercent) @@ -455,21 +455,10 @@ namespace MWGui mSpellStatus->setProgressRange(100); mSpellStatus->setProgressPosition(chargePercent); - if (mSpellImage->getChildCount()) - MyGUI::Gui::getInstance().destroyWidget(mSpellImage->getChildAt(0)); - mSpellBox->setUserString("ToolTipType", "ItemPtr"); mSpellBox->setUserData(item); - mSpellImage->setImageTexture("textures\\menu_icon_magic_mini.dds"); - MyGUI::ImageBox* itemBox = mSpellImage->createWidgetReal("ImageBox", MyGUI::FloatCoord(0,0,1,1) - , MyGUI::Align::Stretch); - - std::string path = std::string("icons\\"); - path+=item.getClass().getInventoryIcon(item); - Widgets::fixTexturePath(path); - itemBox->setImageTexture(path); - itemBox->setNeedMouseFocus(false); + mSpellImage->setItem(item); } void HUD::setSelectedWeapon(const MWWorld::Ptr& item, int durabilityPercent) @@ -489,23 +478,7 @@ namespace MWGui mWeapStatus->setProgressRange(100); mWeapStatus->setProgressPosition(durabilityPercent); - if (mWeapImage->getChildCount()) - MyGUI::Gui::getInstance().destroyWidget(mWeapImage->getChildAt(0)); - - std::string path = std::string("icons\\"); - path+=item.getClass().getInventoryIcon(item); - Widgets::fixTexturePath(path); - - if (item.getClass().getEnchantment(item) != "") - { - mWeapImage->setImageTexture("textures\\menu_icon_magic_mini.dds"); - MyGUI::ImageBox* itemBox = mWeapImage->createWidgetReal("ImageBox", MyGUI::FloatCoord(0,0,1,1) - , MyGUI::Align::Stretch); - itemBox->setImageTexture(path); - itemBox->setNeedMouseFocus(false); - } - else - mWeapImage->setImageTexture(path); + mWeapImage->setItem(item); } void HUD::unsetSelectedSpell() @@ -519,11 +492,9 @@ namespace MWGui mWeaponSpellBox->setVisible(true); } - if (mSpellImage->getChildCount()) - MyGUI::Gui::getInstance().destroyWidget(mSpellImage->getChildAt(0)); mSpellStatus->setProgressRange(100); mSpellStatus->setProgressPosition(0); - mSpellImage->setImageTexture(""); + mSpellImage->setItem(MWWorld::Ptr()); mSpellBox->clearUserStrings(); } @@ -538,17 +509,17 @@ namespace MWGui mWeaponSpellBox->setVisible(true); } - if (mWeapImage->getChildCount()) - MyGUI::Gui::getInstance().destroyWidget(mWeapImage->getChildAt(0)); mWeapStatus->setProgressRange(100); mWeapStatus->setProgressPosition(0); MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = world->getPlayerPtr(); + + mWeapImage->setItem(MWWorld::Ptr()); if (player.getClass().getNpcStats(player).isWerewolf()) - mWeapImage->setImageTexture("icons\\k\\tx_werewolf_hand.dds"); + mWeapImage->setIcon("icons\\k\\tx_werewolf_hand.dds"); else - mWeapImage->setImageTexture("icons\\k\\stealth_handtohand.dds"); + mWeapImage->setIcon("icons\\k\\stealth_handtohand.dds"); mWeapBox->clearUserStrings(); } diff --git a/apps/openmw/mwgui/hud.hpp b/apps/openmw/mwgui/hud.hpp index 973ac0745..f451ea4d2 100644 --- a/apps/openmw/mwgui/hud.hpp +++ b/apps/openmw/mwgui/hud.hpp @@ -10,6 +10,7 @@ namespace MWGui { class DragAndDrop; class SpellIcons; + class ItemWidget; class HUD : public OEngine::GUI::Layout, public LocalMapBase { @@ -63,7 +64,7 @@ namespace MWGui MyGUI::ProgressBar *mHealth, *mMagicka, *mStamina, *mEnemyHealth, *mDrowning; MyGUI::Widget* mHealthFrame; MyGUI::Widget *mWeapBox, *mSpellBox, *mSneakBox; - MyGUI::ImageBox *mWeapImage, *mSpellImage; + ItemWidget *mWeapImage, *mSpellImage; MyGUI::ProgressBar *mWeapStatus, *mSpellStatus; MyGUI::Widget *mEffectBox, *mMinimapBox; MyGUI::Button* mMinimapButton; diff --git a/apps/openmw/mwgui/itemview.cpp b/apps/openmw/mwgui/itemview.cpp index 24bc3fd63..fdaf93039 100644 --- a/apps/openmw/mwgui/itemview.cpp +++ b/apps/openmw/mwgui/itemview.cpp @@ -12,6 +12,7 @@ #include "../mwworld/class.hpp" #include "itemmodel.hpp" +#include "itemwidget.hpp" namespace MWGui { @@ -80,53 +81,24 @@ void ItemView::update() const ItemStack& item = mModel->getItem(i); /// \todo performance improvement: don't create/destroy all the widgets everytime the container window changes size, only reposition them - std::string path = std::string("icons\\"); - path += item.mBase.getClass().getInventoryIcon(item.mBase); - - // background widget (for the "equipped" frame and magic item background image) - bool isMagic = (item.mFlags & ItemStack::Flag_Enchanted); - MyGUI::ImageBox* backgroundWidget = dragArea->createWidget("ImageBox", + ItemWidget* itemWidget = dragArea->createWidget("MW_ItemIcon", MyGUI::IntCoord(x, y, 42, 42), MyGUI::Align::Default); - backgroundWidget->setUserString("ToolTipType", "ItemModelIndex"); - backgroundWidget->setUserData(std::make_pair(i, mModel)); - - std::string backgroundTex = "textures\\menu_icon"; - if (isMagic) - backgroundTex += "_magic"; - if (item.mType == ItemStack::Type_Normal) - { - if (!isMagic) - backgroundTex = ""; - } - else if (item.mType == ItemStack::Type_Equipped) - backgroundTex += "_equip"; - else if (item.mType == ItemStack::Type_Barter) - backgroundTex += "_barter"; - - if (backgroundTex != "") - backgroundTex += ".dds"; - - backgroundWidget->setImageTexture(backgroundTex); - if ((item.mType == ItemStack::Type_Barter) && !isMagic) - backgroundWidget->setProperty("ImageCoord", "2 2 42 42"); - else - backgroundWidget->setProperty("ImageCoord", "0 0 42 42"); - backgroundWidget->eventMouseButtonClick += MyGUI::newDelegate(this, &ItemView::onSelectedItem); - backgroundWidget->eventMouseWheel += MyGUI::newDelegate(this, &ItemView::onMouseWheel); - - // image - MyGUI::ImageBox* image = backgroundWidget->createWidget("ImageBox", - MyGUI::IntCoord(5, 5, 32, 32), MyGUI::Align::Default); - std::string::size_type pos = path.rfind("."); - if(pos != std::string::npos) - path.erase(pos); - path.append(".dds"); - image->setImageTexture(path); - image->setNeedMouseFocus(false); + itemWidget->setUserString("ToolTipType", "ItemModelIndex"); + itemWidget->setUserData(std::make_pair(i, mModel)); + ItemWidget::ItemState state = ItemWidget::None; + if (item.mType == ItemStack::Type_Barter) + state = ItemWidget::Barter; + if (item.mType == ItemStack::Type_Equipped) + state = ItemWidget::Equip; + itemWidget->setItem(item.mBase, state); + + itemWidget->eventMouseButtonClick += MyGUI::newDelegate(this, &ItemView::onSelectedItem); + itemWidget->eventMouseWheel += MyGUI::newDelegate(this, &ItemView::onMouseWheel); // text widget that shows item count - MyGUI::TextBox* text = image->createWidget("SandBrightText", - MyGUI::IntCoord(0, 14, 32, 18), MyGUI::Align::Default, std::string("Label")); + // TODO: move to ItemWidget + MyGUI::TextBox* text = itemWidget->createWidget("SandBrightText", + MyGUI::IntCoord(5, 19, 32, 18), MyGUI::Align::Default, std::string("Label")); text->setTextAlign(MyGUI::Align::Right); text->setNeedMouseFocus(false); text->setTextShadow(true); diff --git a/apps/openmw/mwgui/itemwidget.cpp b/apps/openmw/mwgui/itemwidget.cpp new file mode 100644 index 000000000..a1ca5cb6c --- /dev/null +++ b/apps/openmw/mwgui/itemwidget.cpp @@ -0,0 +1,105 @@ +#include "itemwidget.hpp" + +#include +#include + +#include "../mwworld/class.hpp" + +namespace MWGui +{ + + ItemWidget::ItemWidget() + : mItem(NULL) + { + + } + + void ItemWidget::registerComponents() + { + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + } + + void ItemWidget::initialiseOverride() + { + assignWidget(mItem, "Item"); + if (mItem) + mItem->setNeedMouseFocus(false); + assignWidget(mFrame, "Frame"); + if (mFrame) + mFrame->setNeedMouseFocus(false); + + Base::initialiseOverride(); + } + + void ItemWidget::setIcon(const std::string &icon) + { + if (mItem) + mItem->setImageTexture(icon); + } + + void ItemWidget::setFrame(const std::string &frame, const MyGUI::IntCoord &coord) + { + if (mFrame) + { + mFrame->setImageTexture(frame); + mFrame->setImageTile(MyGUI::IntSize(coord.width, coord.height)); // Why is this needed? MyGUI bug? + mFrame->setImageCoord(coord); + } + } + + void ItemWidget::setIcon(const MWWorld::Ptr &ptr) + { + // image + std::string path = std::string("icons\\"); + path += ptr.getClass().getInventoryIcon(ptr); + + std::string::size_type pos = path.rfind("."); + if(pos != std::string::npos) + path.erase(pos); + path.append(".dds"); + setIcon(path); + } + + + void ItemWidget::setItem(const MWWorld::Ptr &ptr, ItemState state) + { + if (!mItem) + return; + + if (ptr.isEmpty()) + { + if (mFrame) + mFrame->setImageTexture(""); + mItem->setImageTexture(""); + return; + } + + bool isMagic = !ptr.getClass().getEnchantment(ptr).empty(); + + std::string backgroundTex = "textures\\menu_icon"; + if (isMagic) + backgroundTex += "_magic"; + if (state == None) + { + if (!isMagic) + backgroundTex = ""; + } + else if (state == Equip) + { + backgroundTex += "_equip"; + } + else if (state == Barter) + backgroundTex += "_barter"; + + if (backgroundTex != "") + backgroundTex += ".dds"; + + if (state == Barter && !isMagic) + setFrame(backgroundTex, MyGUI::IntCoord(2,2,42,42)); + else + setFrame(backgroundTex, MyGUI::IntCoord(0,0,42,42)); + + setIcon(ptr); + } + +} diff --git a/apps/openmw/mwgui/itemwidget.hpp b/apps/openmw/mwgui/itemwidget.hpp new file mode 100644 index 000000000..3de98489d --- /dev/null +++ b/apps/openmw/mwgui/itemwidget.hpp @@ -0,0 +1,49 @@ +#ifndef OPENMW_MWGUI_ITEMWIDGET_H +#define OPENMW_MWGUI_ITEMWIDGET_H + +#include + +namespace MWWorld +{ + class Ptr; +} + +namespace MWGui +{ + + /// @brief A widget that shows an icon for an MWWorld::Ptr + class ItemWidget : public MyGUI::Widget + { + MYGUI_RTTI_DERIVED(ItemWidget) + public: + ItemWidget(); + + /// Register needed components with MyGUI's factory manager + static void registerComponents (); + + enum ItemState + { + None, + Equip, + Barter, + Magic + }; + + /// \a ptr may be empty + void setItem (const MWWorld::Ptr& ptr, ItemState state = None); + + // Set icon and frame manually + void setIcon (const std::string& icon); + void setIcon (const MWWorld::Ptr& ptr); + void setFrame (const std::string& frame, const MyGUI::IntCoord& coord); + + private: + virtual void initialiseOverride(); + + MyGUI::ImageBox* mItem; + MyGUI::ImageBox* mFrame; + }; + +} + +#endif diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index f1bbc68cd..f6d6b8135 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -136,7 +136,7 @@ namespace MWGui Ogre::StringVector groups = Ogre::ResourceGroupManager::getSingleton().getResourceGroups (); for (Ogre::StringVector::iterator it = groups.begin(); it != groups.end(); ++it) { - Ogre::StringVectorPtr resourcesInThisGroup = Ogre::ResourceGroupManager::getSingleton ().findResourceNames (*it, "Splash_*.tga"); + Ogre::StringVectorPtr resourcesInThisGroup = Ogre::ResourceGroupManager::getSingleton ().findResourceNames (*it, "Splash/*.tga"); mResources.insert(mResources.end(), resourcesInThisGroup->begin(), resourcesInThisGroup->end()); } } diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp index b5cd61f59..ca7e877cc 100644 --- a/apps/openmw/mwgui/mainmenu.cpp +++ b/apps/openmw/mwgui/mainmenu.cpp @@ -68,10 +68,10 @@ namespace MWGui { if (visible) updateMenu(); - else - showBackground( - MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu) && - MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame); + + showBackground( + MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu) && + MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame); OEngine::GUI::Layout::setVisible (visible); } @@ -167,7 +167,7 @@ namespace MWGui mVideo = mVideoBackground->createWidget("ImageBox", 0,0,1,1, MyGUI::Align::Stretch, "Menu"); - mVideo->playVideo("video\\menu_background.bik", false); + mVideo->playVideo("video\\menu_background.bik"); } MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); @@ -204,7 +204,7 @@ namespace MWGui if (!mVideo->update()) { // If finished playing, start again - mVideo->playVideo("video\\menu_background.bik", 0); + mVideo->playVideo("video\\menu_background.bik"); } } } @@ -220,7 +220,6 @@ namespace MWGui MWBase::StateManager::State state = MWBase::Environment::get().getStateManager()->getState(); - showBackground(state == MWBase::StateManager::State_NoGame); mVersionText->setVisible(state == MWBase::StateManager::State_NoGame); std::vector buttons; diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index 1ce167c33..d73a0e122 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -179,7 +179,6 @@ namespace MWGui { WindowModal::open(); - int fixedWidth = 500; int textPadding = 10; // padding between text-widget and main-widget int textButtonPadding = 20; // padding between the text-widget und the button-widget int buttonLeftPadding = 10; // padding between the buttons if horizontal @@ -232,102 +231,52 @@ namespace MWGui buttonsWidth += buttonLeftPadding; MyGUI::IntSize mainWidgetSize; - if(buttonsWidth < fixedWidth) - { - // on one line - if(textSize.width + 2*textPadding < buttonsWidth) - { - mainWidgetSize.width = buttonsWidth; - } - else - { - mainWidgetSize.width = textSize.width + 3*textPadding; - } - mainWidgetSize.height = textSize.height + textButtonPadding + buttonHeight + buttonMainPadding; - - MyGUI::IntPoint absPos; - absPos.left = (gameWindowSize.width - mainWidgetSize.width)/2; - absPos.top = (gameWindowSize.height - mainWidgetSize.height)/2; - - mMainWidget->setPosition(absPos); - mMainWidget->setSize(mainWidgetSize); - - MyGUI::IntCoord messageWidgetCoord; - messageWidgetCoord.left = (mainWidgetSize.width - textSize.width)/2; - messageWidgetCoord.top = textPadding; - mMessageWidget->setCoord(messageWidgetCoord); - - mMessageWidget->setSize(textSize); - - MyGUI::IntCoord buttonCord; - MyGUI::IntSize buttonSize(0, buttonHeight); - int left = (mainWidgetSize.width - buttonsWidth)/2 + buttonPadding; - - std::vector::const_iterator button; - for(button = mButtons.begin(); button != mButtons.end(); ++button) - { - buttonCord.left = left; - buttonCord.top = textSize.height + textButtonPadding; - - buttonSize.width = (*button)->getTextSize().width + 2*buttonPadding; - buttonSize.height = (*button)->getTextSize().height + 2*buttonPadding; - - (*button)->setCoord(buttonCord); - (*button)->setSize(buttonSize); - - left += buttonSize.width + buttonLeftPadding; - } + // among each other + if(biggestButtonWidth > textSize.width) { + mainWidgetSize.width = biggestButtonWidth + buttonTopPadding; + } + else { + mainWidgetSize.width = textSize.width + 3*textPadding; } - else - { - // among each other - if(biggestButtonWidth > textSize.width) { - mainWidgetSize.width = biggestButtonWidth + buttonTopPadding; - } - else { - mainWidgetSize.width = textSize.width + 3*textPadding; - } - MyGUI::IntCoord buttonCord; - MyGUI::IntSize buttonSize(0, buttonHeight); + MyGUI::IntCoord buttonCord; + MyGUI::IntSize buttonSize(0, buttonHeight); - int top = textButtonPadding + buttonTopPadding + textSize.height; + int top = textButtonPadding + buttonTopPadding + textSize.height; - std::vector::const_iterator button; - for(button = mButtons.begin(); button != mButtons.end(); ++button) - { - buttonSize.width = (*button)->getTextSize().width + buttonPadding*2; - buttonSize.height = (*button)->getTextSize().height + buttonPadding*2; + std::vector::const_iterator button; + for(button = mButtons.begin(); button != mButtons.end(); ++button) + { + buttonSize.width = (*button)->getTextSize().width + buttonPadding*2; + buttonSize.height = (*button)->getTextSize().height + buttonPadding*2; - buttonCord.top = top; - buttonCord.left = (mainWidgetSize.width - buttonSize.width)/2 - 5; // FIXME: -5 is not so nice :/ + buttonCord.top = top; + buttonCord.left = (mainWidgetSize.width - buttonSize.width)/2 - 5; // FIXME: -5 is not so nice :/ - (*button)->setCoord(buttonCord); - (*button)->setSize(buttonSize); + (*button)->setCoord(buttonCord); + (*button)->setSize(buttonSize); - top += buttonSize.height + 2*buttonTopPadding; - } + top += buttonSize.height + 2*buttonTopPadding; + } - mainWidgetSize.height = top + buttonMainPadding; - mMainWidget->setSize(mainWidgetSize); + mainWidgetSize.height = top + buttonMainPadding; + mMainWidget->setSize(mainWidgetSize); - MyGUI::IntPoint absPos; - absPos.left = (gameWindowSize.width - mainWidgetSize.width)/2; - absPos.top = (gameWindowSize.height - mainWidgetSize.height)/2; + MyGUI::IntPoint absPos; + absPos.left = (gameWindowSize.width - mainWidgetSize.width)/2; + absPos.top = (gameWindowSize.height - mainWidgetSize.height)/2; - mMainWidget->setPosition(absPos); + mMainWidget->setPosition(absPos); - MyGUI::IntCoord messageWidgetCoord; - messageWidgetCoord.left = (mainWidgetSize.width - textSize.width)/2; - messageWidgetCoord.top = textPadding; - messageWidgetCoord.width = textSize.width; - messageWidgetCoord.height = textSize.height; - mMessageWidget->setCoord(messageWidgetCoord); - } + MyGUI::IntCoord messageWidgetCoord; + messageWidgetCoord.left = (mainWidgetSize.width - textSize.width)/2; + messageWidgetCoord.top = textPadding; + messageWidgetCoord.width = textSize.width; + messageWidgetCoord.height = textSize.height; + mMessageWidget->setCoord(messageWidgetCoord); // Set key focus to "Ok" button std::string ok = Misc::StringUtils::lowerCase(MyGUI::LanguageManager::getInstance().replaceTags("#{sOK}")); - std::vector::const_iterator button; for(button = mButtons.begin(); button != mButtons.end(); ++button) { if(Misc::StringUtils::ciEqual((*button)->getCaption(), ok)) diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index af4e20ca4..328ef21fb 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -23,6 +23,9 @@ #include "spellwindow.hpp" +#include "itemwidget.hpp" +#include "sortfilteritemmodel.hpp" + namespace MWGui { @@ -46,14 +49,16 @@ namespace MWGui for (int i = 0; i < 10; ++i) { - MyGUI::Button* button; + ItemWidget* button; getWidget(button, "QuickKey" + boost::lexical_cast(i+1)); button->eventMouseButtonClick += MyGUI::newDelegate(this, &QuickKeysMenu::onQuickKeyButtonClicked); - unassign(button, i); - mQuickKeyButtons.push_back(button); + + mAssigned.push_back(Type_Unassigned); + + unassign(button, i); } } @@ -77,12 +82,14 @@ namespace MWGui delete mMagicSelectionDialog; } - void QuickKeysMenu::unassign(MyGUI::Widget* key, int index) + void QuickKeysMenu::unassign(ItemWidget* key, int index) { - while (key->getChildCount ()) - MyGUI::Gui::getInstance ().destroyWidget (key->getChildAt(0)); + key->clearUserStrings(); + key->setItem(MWWorld::Ptr()); + while (key->getChildCount()) // Destroy number label + MyGUI::Gui::getInstance().destroyWidget(key->getChildAt(0)); - key->setUserData(Type_Unassigned); + mAssigned[index] = Type_Unassigned; MyGUI::TextBox* textBox = key->createWidgetReal("SandText", MyGUI::FloatCoord(0,0,1,1), MyGUI::Align::Default); textBox->setTextAlign (MyGUI::Align::Center); @@ -128,6 +135,7 @@ namespace MWGui } mItemSelectionDialog->setVisible(true); mItemSelectionDialog->openContainer(MWBase::Environment::get().getWorld()->getPlayerPtr()); + mItemSelectionDialog->setFilter(SortFilterItemModel::Filter_OnlyUsableItems); mAssignDialog->setVisible (false); } @@ -156,27 +164,16 @@ namespace MWGui void QuickKeysMenu::onAssignItem(MWWorld::Ptr item) { - MyGUI::Button* button = mQuickKeyButtons[mSelectedIndex]; - while (button->getChildCount ()) - MyGUI::Gui::getInstance ().destroyWidget (button->getChildAt(0)); - - button->setUserData(Type_Item); - - MyGUI::ImageBox* frame = button->createWidget("ImageBox", MyGUI::IntCoord(9, 8, 42, 42), MyGUI::Align::Default); - std::string backgroundTex = "textures\\menu_icon_barter.dds"; - frame->setImageTexture (backgroundTex); - frame->setImageCoord (MyGUI::IntCoord(4, 4, 40, 40)); - frame->setUserString ("ToolTipType", "ItemPtr"); - frame->setUserData(item); - frame->eventMouseButtonClick += MyGUI::newDelegate(this, &QuickKeysMenu::onQuickKeyButtonClicked); - MyGUI::ImageBox* image = frame->createWidget("ImageBox", MyGUI::IntCoord(5, 5, 32, 32), MyGUI::Align::Default); - std::string path = std::string("icons\\"); - path += item.getClass().getInventoryIcon(item); - int pos = path.rfind("."); - path.erase(pos); - path.append(".dds"); - image->setImageTexture (path); - image->setNeedMouseFocus (false); + assert (mSelectedIndex >= 0); + ItemWidget* button = mQuickKeyButtons[mSelectedIndex]; + while (button->getChildCount()) // Destroy number label + MyGUI::Gui::getInstance().destroyWidget(button->getChildAt(0)); + + mAssigned[mSelectedIndex] = Type_Item; + + button->setItem(item, ItemWidget::Barter); + button->setUserString ("ToolTipType", "ItemPtr"); + button->setUserData(item); if (mItemSelectionDialog) mItemSelectionDialog->setVisible(false); @@ -189,28 +186,18 @@ namespace MWGui void QuickKeysMenu::onAssignMagicItem (MWWorld::Ptr item) { - MyGUI::Button* button = mQuickKeyButtons[mSelectedIndex]; - while (button->getChildCount ()) - MyGUI::Gui::getInstance ().destroyWidget (button->getChildAt(0)); - - button->setUserData(Type_MagicItem); - - MyGUI::ImageBox* frame = button->createWidget("ImageBox", MyGUI::IntCoord(9, 8, 42, 42), MyGUI::Align::Default); - std::string backgroundTex = "textures\\menu_icon_select_magic_magic.dds"; - frame->setImageTexture (backgroundTex); - frame->setImageCoord (MyGUI::IntCoord(2, 2, 40, 40)); - frame->setUserString ("ToolTipType", "ItemPtr"); - frame->setUserData(item); - frame->eventMouseButtonClick += MyGUI::newDelegate(this, &QuickKeysMenu::onQuickKeyButtonClicked); - - MyGUI::ImageBox* image = frame->createWidget("ImageBox", MyGUI::IntCoord(5, 5, 32, 32), MyGUI::Align::Default); - std::string path = std::string("icons\\"); - path += item.getClass().getInventoryIcon(item); - int pos = path.rfind("."); - path.erase(pos); - path.append(".dds"); - image->setImageTexture (path); - image->setNeedMouseFocus (false); + assert (mSelectedIndex >= 0); + ItemWidget* button = mQuickKeyButtons[mSelectedIndex]; + while (button->getChildCount()) // Destroy number label + MyGUI::Gui::getInstance().destroyWidget(button->getChildAt(0)); + + mAssigned[mSelectedIndex] = Type_MagicItem; + + button->setFrame("textures\\menu_icon_select_magic_magic.dds", MyGUI::IntCoord(2, 2, 40, 40)); + button->setIcon(item); + + button->setUserString ("ToolTipType", "ItemPtr"); + button->setUserData(item); if (mMagicSelectionDialog) mMagicSelectionDialog->setVisible(false); @@ -218,21 +205,16 @@ namespace MWGui void QuickKeysMenu::onAssignMagic (const std::string& spellId) { - MyGUI::Button* button = mQuickKeyButtons[mSelectedIndex]; - while (button->getChildCount ()) - MyGUI::Gui::getInstance ().destroyWidget (button->getChildAt(0)); - - button->setUserData(Type_Magic); + assert (mSelectedIndex >= 0); + ItemWidget* button = mQuickKeyButtons[mSelectedIndex]; + while (button->getChildCount()) // Destroy number label + MyGUI::Gui::getInstance().destroyWidget(button->getChildAt(0)); - MyGUI::ImageBox* frame = button->createWidget("ImageBox", MyGUI::IntCoord(9, 8, 42, 42), MyGUI::Align::Default); - std::string backgroundTex = "textures\\menu_icon_select_magic.dds"; - frame->setImageTexture (backgroundTex); - frame->setImageCoord (MyGUI::IntCoord(2, 2, 40, 40)); - frame->setUserString ("ToolTipType", "Spell"); - frame->setUserString ("Spell", spellId); - frame->eventMouseButtonClick += MyGUI::newDelegate(this, &QuickKeysMenu::onQuickKeyButtonClicked); + mAssigned[mSelectedIndex] = Type_Magic; - MyGUI::ImageBox* image = frame->createWidget("ImageBox", MyGUI::IntCoord(5, 5, 32, 32), MyGUI::Align::Default); + button->setItem(MWWorld::Ptr()); + button->setUserString ("ToolTipType", "Spell"); + button->setUserString ("Spell", spellId); const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); @@ -251,8 +233,8 @@ namespace MWGui path.erase(pos); path.append(".dds"); - image->setImageTexture (path); - image->setNeedMouseFocus (false); + button->setFrame("textures\\menu_icon_select_magic.dds", MyGUI::IntCoord(2, 2, 40, 40)); + button->setIcon(path); if (mMagicSelectionDialog) mMagicSelectionDialog->setVisible(false); @@ -265,16 +247,17 @@ namespace MWGui void QuickKeysMenu::activateQuickKey(int index) { - MyGUI::Button* button = mQuickKeyButtons[index-1]; + assert (index-1 >= 0); + ItemWidget* button = mQuickKeyButtons[index-1]; - QuickKeyType type = *button->getUserData(); + QuickKeyType type = mAssigned[index-1]; MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); if (type == Type_Item || type == Type_MagicItem) { - MWWorld::Ptr item = *button->getChildAt (0)->getUserData(); + MWWorld::Ptr item = *button->getUserData(); // make sure the item is available if (item.getRefData ().getCount() < 1) { @@ -286,7 +269,7 @@ namespace MWGui if (Misc::StringUtils::ciEqual(it->getCellRef().getRefId(), id)) { item = *it; - button->getChildAt(0)->setUserData(item); + button->setUserData(item); break; } } @@ -303,7 +286,7 @@ namespace MWGui if (type == Type_Magic) { - std::string spellId = button->getChildAt(0)->getUserString("Spell"); + std::string spellId = button->getUserString("Spell"); // Make sure the player still has this spell MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); @@ -315,13 +298,13 @@ namespace MWGui } else if (type == Type_Item) { - MWWorld::Ptr item = *button->getChildAt (0)->getUserData(); + MWWorld::Ptr item = *button->getUserData(); MWBase::Environment::get().getWindowManager()->getInventoryWindow()->useItem(item); } else if (type == Type_MagicItem) { - MWWorld::Ptr item = *button->getChildAt (0)->getUserData(); + MWWorld::Ptr item = *button->getUserData(); // retrieve ContainerStoreIterator to the item MWWorld::ContainerStoreIterator it = store.begin(); @@ -403,9 +386,9 @@ namespace MWGui for (int i=0; i<10; ++i) { - MyGUI::Button* button = mQuickKeyButtons[i]; + ItemWidget* button = mQuickKeyButtons[i]; - int type = *button->getUserData(); + int type = mAssigned[i]; ESM::QuickKeys::QuickKey key; key.mType = type; @@ -417,12 +400,12 @@ namespace MWGui case Type_Item: case Type_MagicItem: { - MWWorld::Ptr item = *button->getChildAt(0)->getUserData(); + MWWorld::Ptr item = *button->getUserData(); key.mId = item.getCellRef().getRefId(); break; } case Type_Magic: - std::string spellId = button->getChildAt(0)->getUserString("Spell"); + std::string spellId = button->getUserString("Spell"); key.mId = spellId; break; } @@ -452,7 +435,7 @@ namespace MWGui mSelectedIndex = i; int keyType = it->mType; std::string id = it->mId; - MyGUI::Button* button = mQuickKeyButtons[i]; + ItemWidget* button = mQuickKeyButtons[i]; switch (keyType) { diff --git a/apps/openmw/mwgui/quickkeysmenu.hpp b/apps/openmw/mwgui/quickkeysmenu.hpp index 40c5dab56..dc088d3c9 100644 --- a/apps/openmw/mwgui/quickkeysmenu.hpp +++ b/apps/openmw/mwgui/quickkeysmenu.hpp @@ -11,6 +11,7 @@ namespace MWGui class QuickKeysMenuAssign; class ItemSelectionDialog; class MagicSelectionDialog; + class ItemWidget; class QuickKeysMenu : public WindowBase { @@ -51,7 +52,8 @@ namespace MWGui MyGUI::EditBox* mInstructionLabel; MyGUI::Button* mOkButton; - std::vector mQuickKeyButtons; + std::vector mQuickKeyButtons; + std::vector mAssigned; QuickKeysMenuAssign* mAssignDialog; ItemSelectionDialog* mItemSelectionDialog; @@ -63,7 +65,7 @@ namespace MWGui void onQuickKeyButtonClicked(MyGUI::Widget* sender); void onOkButtonClicked(MyGUI::Widget* sender); - void unassign(MyGUI::Widget* key, int index); + void unassign(ItemWidget* key, int index); }; class QuickKeysMenuAssign : public WindowModal diff --git a/apps/openmw/mwgui/recharge.cpp b/apps/openmw/mwgui/recharge.cpp index e5ea54591..079564273 100644 --- a/apps/openmw/mwgui/recharge.cpp +++ b/apps/openmw/mwgui/recharge.cpp @@ -14,6 +14,7 @@ #include "../mwmechanics/npcstats.hpp" #include "widgets.hpp" +#include "itemwidget.hpp" namespace MWGui { @@ -45,12 +46,7 @@ void Recharge::exit() void Recharge::start (const MWWorld::Ptr &item) { - std::string path = std::string("icons\\"); - path += item.getClass().getInventoryIcon(item); - int pos = path.rfind("."); - path.erase(pos); - path.append(".dds"); - mGemIcon->setImageTexture (path); + mGemIcon->setItem(item); mGemIcon->setUserString("ToolTipType", "ItemPtr"); mGemIcon->setUserData(item); @@ -108,14 +104,9 @@ void Recharge::updateView() text->setNeedMouseFocus(false); currentY += 19; - MyGUI::ImageBox* icon = mView->createWidget ( - "ImageBox", MyGUI::IntCoord(16, currentY, 32, 32), MyGUI::Align::Default); - std::string path = std::string("icons\\"); - path += iter->getClass().getInventoryIcon(*iter); - int pos = path.rfind("."); - path.erase(pos); - path.append(".dds"); - icon->setImageTexture (path); + ItemWidget* icon = mView->createWidget ( + "MW_ItemIconSmall", MyGUI::IntCoord(16, currentY, 32, 32), MyGUI::Align::Default); + icon->setItem(*iter); icon->setUserString("ToolTipType", "ItemPtr"); icon->setUserData(*iter); icon->eventMouseButtonClick += MyGUI::newDelegate(this, &Recharge::onItemClicked); diff --git a/apps/openmw/mwgui/recharge.hpp b/apps/openmw/mwgui/recharge.hpp index 5558b197e..17d700649 100644 --- a/apps/openmw/mwgui/recharge.hpp +++ b/apps/openmw/mwgui/recharge.hpp @@ -8,6 +8,8 @@ namespace MWGui { +class ItemWidget; + class Recharge : public WindowBase { public: @@ -25,7 +27,7 @@ protected: MyGUI::Widget* mGemBox; - MyGUI::ImageBox* mGemIcon; + ItemWidget* mGemIcon; MyGUI::TextBox* mChargeLabel; diff --git a/apps/openmw/mwgui/repair.cpp b/apps/openmw/mwgui/repair.cpp index 29ec62887..986e27243 100644 --- a/apps/openmw/mwgui/repair.cpp +++ b/apps/openmw/mwgui/repair.cpp @@ -13,6 +13,8 @@ #include "widgets.hpp" +#include "itemwidget.hpp" + namespace MWGui { @@ -44,12 +46,7 @@ void Repair::startRepairItem(const MWWorld::Ptr &item) { mRepair.setTool(item); - std::string path = std::string("icons\\"); - path += item.getClass().getInventoryIcon(item); - int pos = path.rfind("."); - path.erase(pos); - path.append(".dds"); - mToolIcon->setImageTexture (path); + mToolIcon->setItem(item); mToolIcon->setUserString("ToolTipType", "ItemPtr"); mToolIcon->setUserData(item); @@ -113,14 +110,9 @@ void Repair::updateRepairView() text->setNeedMouseFocus(false); currentY += 19; - MyGUI::ImageBox* icon = mRepairView->createWidget ( - "ImageBox", MyGUI::IntCoord(16, currentY, 32, 32), MyGUI::Align::Default); - std::string path = std::string("icons\\"); - path += iter->getClass().getInventoryIcon(*iter); - int pos = path.rfind("."); - path.erase(pos); - path.append(".dds"); - icon->setImageTexture (path); + ItemWidget* icon = mRepairView->createWidget ( + "MW_ItemIconSmall", MyGUI::IntCoord(16, currentY, 32, 32), MyGUI::Align::Default); + icon->setItem(*iter); icon->setUserString("ToolTipType", "ItemPtr"); icon->setUserData(*iter); icon->eventMouseButtonClick += MyGUI::newDelegate(this, &Repair::onRepairItem); diff --git a/apps/openmw/mwgui/repair.hpp b/apps/openmw/mwgui/repair.hpp index 42539ad9e..439ee1169 100644 --- a/apps/openmw/mwgui/repair.hpp +++ b/apps/openmw/mwgui/repair.hpp @@ -8,6 +8,8 @@ namespace MWGui { +class ItemWidget; + class Repair : public WindowBase { public: @@ -25,7 +27,7 @@ protected: MyGUI::Widget* mToolBox; - MyGUI::ImageBox* mToolIcon; + ItemWidget* mToolIcon; MyGUI::TextBox* mUsesLabel; MyGUI::TextBox* mQualityLabel; diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp index ef5d5858b..a35415e75 100644 --- a/apps/openmw/mwgui/savegamedialog.cpp +++ b/apps/openmw/mwgui/savegamedialog.cpp @@ -30,11 +30,13 @@ namespace MWGui getWidget(mInfoText, "InfoText"); getWidget(mOkButton, "OkButton"); getWidget(mCancelButton, "CancelButton"); + getWidget(mDeleteButton, "DeleteButton"); getWidget(mSaveList, "SaveList"); getWidget(mSaveNameEdit, "SaveNameEdit"); getWidget(mSpacer, "Spacer"); mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onOkButtonClicked); mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onCancelButtonClicked); + mDeleteButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onDeleteButtonClicked); mCharacterSelection->eventComboChangePosition += MyGUI::newDelegate(this, &SaveGameDialog::onCharacterSelected); mSaveList->eventListChangePosition += MyGUI::newDelegate(this, &SaveGameDialog::onSlotSelected); mSaveList->eventListMouseItemActivate += MyGUI::newDelegate(this, &SaveGameDialog::onSlotMouseClick); @@ -54,13 +56,16 @@ namespace MWGui onSlotSelected(sender, pos); if (pos != MyGUI::ITEM_NONE && MyGUI::InputManager::getInstance().isShiftPressed()) - { - ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog(); - dialog->open("#{sMessage3}"); - dialog->eventOkClicked.clear(); - dialog->eventOkClicked += MyGUI::newDelegate(this, &SaveGameDialog::onDeleteSlotConfirmed); - dialog->eventCancelClicked.clear(); - } + confirmDeleteSave(); + } + + void SaveGameDialog::confirmDeleteSave() + { + ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog(); + dialog->open("#{sMessage3}"); + dialog->eventOkClicked.clear(); + dialog->eventOkClicked += MyGUI::newDelegate(this, &SaveGameDialog::onDeleteSlotConfirmed); + dialog->eventCancelClicked.clear(); } void SaveGameDialog::onDeleteSlotConfirmed() @@ -107,6 +112,7 @@ namespace MWGui mCurrentCharacter = NULL; mCurrentSlot = NULL; mSaveList->removeAllItems(); + onSlotSelected(mSaveList, MyGUI::ITEM_NONE); MWBase::StateManager* mgr = MWBase::Environment::get().getStateManager(); if (mgr->characterBegin() == mgr->characterEnd()) @@ -157,6 +163,8 @@ namespace MWGui } mCharacterSelection->setIndexSelected(selectedIndex); + if (selectedIndex == MyGUI::ITEM_NONE) + mCharacterSelection->setCaption("Select Character ..."); fillSaveList(); @@ -175,6 +183,9 @@ namespace MWGui mCharacterSelection->setVisible(load); mSpacer->setUserString("Hidden", load ? "false" : "true"); + mDeleteButton->setUserString("Hidden", load ? "false" : "true"); + mDeleteButton->setVisible(load); + if (!load) { mCurrentCharacter = MWBase::Environment::get().getStateManager()->getCurrentCharacter (false); @@ -188,6 +199,12 @@ namespace MWGui exit(); } + void SaveGameDialog::onDeleteButtonClicked(MyGUI::Widget *sender) + { + if (mCurrentSlot) + confirmDeleteSave(); + } + void SaveGameDialog::onConfirmationGiven() { accept(true); @@ -225,10 +242,8 @@ namespace MWGui } else { - if (mCurrentCharacter && mCurrentSlot) - { - MWBase::Environment::get().getStateManager()->loadGame (mCurrentCharacter, mCurrentSlot); - } + assert (mCurrentCharacter && mCurrentSlot); + MWBase::Environment::get().getStateManager()->loadGame (mCurrentCharacter, mCurrentSlot); } } @@ -278,6 +293,9 @@ namespace MWGui void SaveGameDialog::onSlotSelected(MyGUI::ListBox *sender, size_t pos) { + mOkButton->setEnabled(pos != MyGUI::ITEM_NONE || mSaving); + mDeleteButton->setEnabled(pos != MyGUI::ITEM_NONE); + if (pos == MyGUI::ITEM_NONE) { mCurrentSlot = NULL; diff --git a/apps/openmw/mwgui/savegamedialog.hpp b/apps/openmw/mwgui/savegamedialog.hpp index 9f44d5370..80cfad279 100644 --- a/apps/openmw/mwgui/savegamedialog.hpp +++ b/apps/openmw/mwgui/savegamedialog.hpp @@ -24,8 +24,11 @@ namespace MWGui void setLoadOrSave(bool load); private: + void confirmDeleteSave(); + void onCancelButtonClicked (MyGUI::Widget* sender); void onOkButtonClicked (MyGUI::Widget* sender); + void onDeleteButtonClicked (MyGUI::Widget* sender); void onCharacterSelected (MyGUI::ComboBox* sender, size_t pos); // Slot selected (mouse click or arrow keys) void onSlotSelected (MyGUI::ListBox* sender, size_t pos); @@ -51,6 +54,7 @@ namespace MWGui MyGUI::EditBox* mInfoText; MyGUI::Button* mOkButton; MyGUI::Button* mCancelButton; + MyGUI::Button* mDeleteButton; MyGUI::ListBox* mSaveList; MyGUI::EditBox* mSaveNameEdit; MyGUI::Widget* mSpacer; diff --git a/apps/openmw/mwgui/sortfilteritemmodel.cpp b/apps/openmw/mwgui/sortfilteritemmodel.cpp index b8dcbcbbb..93e5432ca 100644 --- a/apps/openmw/mwgui/sortfilteritemmodel.cpp +++ b/apps/openmw/mwgui/sortfilteritemmodel.cpp @@ -14,6 +14,7 @@ #include #include "../mwworld/class.hpp" +#include "../mwworld/nullaction.hpp" namespace { @@ -126,6 +127,10 @@ namespace MWGui && !base.get()->mBase->mData.mIsScroll) return false; + if ((mFilter & Filter_OnlyUsableItems) && typeid(*base.getClass().use(base)) == typeid(MWWorld::NullAction) + && base.getClass().getScript(base).empty()) + return false; + return true; } diff --git a/apps/openmw/mwgui/sortfilteritemmodel.hpp b/apps/openmw/mwgui/sortfilteritemmodel.hpp index c7feaa3b9..4af35e7a8 100644 --- a/apps/openmw/mwgui/sortfilteritemmodel.hpp +++ b/apps/openmw/mwgui/sortfilteritemmodel.hpp @@ -36,6 +36,7 @@ namespace MWGui static const int Filter_OnlyEnchanted = (1<<1); static const int Filter_OnlyEnchantable = (1<<2); static const int Filter_OnlyChargedSoulstones = (1<<3); + static const int Filter_OnlyUsableItems = (1<<4); // Only items with a Use action private: diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index aeb79a938..16b010908 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -400,7 +400,7 @@ namespace MWGui if (!info.effects.empty()) { MyGUI::Widget* effectArea = mDynamicToolTipBox->createWidget("", - MyGUI::IntCoord(0, totalSize.height, 300, 300-totalSize.height), + MyGUI::IntCoord(padding.left, totalSize.height, 300-padding.left, 300-totalSize.height), MyGUI::Align::Stretch, "ToolTipEffectArea"); MyGUI::IntCoord coord(0, 6, totalSize.width, 24); @@ -419,7 +419,7 @@ namespace MWGui { assert(enchant); MyGUI::Widget* enchantArea = mDynamicToolTipBox->createWidget("", - MyGUI::IntCoord(0, totalSize.height, 300, 300-totalSize.height), + MyGUI::IntCoord(padding.left, totalSize.height, 300-padding.left, 300-totalSize.height), MyGUI::Align::Stretch, "ToolTipEnchantArea"); MyGUI::IntCoord coord(0, 6, totalSize.width, 24); @@ -512,7 +512,11 @@ namespace MWGui std::string ToolTips::toString(const float value) { std::ostringstream stream; - stream << std::setprecision(3) << value; + + if (value != int(value)) + stream << std::setprecision(3); + + stream << value; return stream.str(); } diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index 73f48c8cf..7ef472282 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -345,7 +345,7 @@ namespace MWGui x += abs(int(npcTerm - pcTerm)); int roll = std::rand()%100 + 1; - if(roll > x) //trade refused + if(roll > x || (mCurrentMerchantOffer < 0) != (mCurrentBalance < 0)) //trade refused { MWBase::Environment::get().getWindowManager()-> messageBox("#{sNotifyMessage9}"); diff --git a/apps/openmw/mwgui/trainingwindow.cpp b/apps/openmw/mwgui/trainingwindow.cpp index 9a2c3b805..6463db3d7 100644 --- a/apps/openmw/mwgui/trainingwindow.cpp +++ b/apps/openmw/mwgui/trainingwindow.cpp @@ -16,6 +16,24 @@ #include "tooltips.hpp" +namespace +{ +// Sorts a container descending by skill value. If skill value is equal, sorts ascending by skill ID. +// pair +bool sortSkills (const std::pair& left, const std::pair& right) +{ + if (left == right) + return false; + + if (left.second > right.second) + return true; + else if (left.second < right.second) + return false; + + return left.first < right.first; +} +} + namespace MWGui { @@ -52,29 +70,17 @@ namespace MWGui MWMechanics::NpcStats& npcStats = actor.getClass().getNpcStats (actor); // NPC can train you in his best 3 skills - std::vector< std::pair > bestSkills; - bestSkills.push_back (std::make_pair(-1, -1)); - bestSkills.push_back (std::make_pair(-1, -1)); - bestSkills.push_back (std::make_pair(-1, -1)); + std::vector< std::pair > skills; for (int i=0; i bestSkills[j].second) - { - if (j<2) - { - bestSkills[j+1] = bestSkills[j]; - } - bestSkills[j] = std::make_pair(i, value); - break; - } - } + skills.push_back(std::make_pair(i, value)); } + std::sort(skills.begin(), skills.end(), sortSkills); + MyGUI::EnumeratorWidgetPtr widgets = mTrainingOptions->getEnumerator (); MyGUI::Gui::getInstance ().destroyWidgets (widgets); @@ -86,20 +92,20 @@ namespace MWGui for (int i=0; i<3; ++i) { int price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer - (mPtr,pcStats.getSkill (bestSkills[i].first).getBase() * gmst.find("iTrainingMod")->getInt (),true); + (mPtr,pcStats.getSkill (skills[i].first).getBase() * gmst.find("iTrainingMod")->getInt (),true); MyGUI::Button* button = mTrainingOptions->createWidget("SandTextButton", MyGUI::IntCoord(5, 5+i*18, mTrainingOptions->getWidth()-10, 18), MyGUI::Align::Default); button->setEnabled(price <= playerGold); - button->setUserData(bestSkills[i].first); + button->setUserData(skills[i].first); button->eventMouseButtonClick += MyGUI::newDelegate(this, &TrainingWindow::onTrainingSelected); - button->setCaptionWithReplacing("#{" + ESM::Skill::sSkillNameIds[bestSkills[i].first] + "} - " + boost::lexical_cast(price)); + button->setCaptionWithReplacing("#{" + ESM::Skill::sSkillNameIds[skills[i].first] + "} - " + boost::lexical_cast(price)); button->setSize(button->getTextSize ().width+12, button->getSize().height); - ToolTips::createSkillToolTip (button, bestSkills[i].first); + ToolTips::createSkillToolTip (button, skills[i].first); } center(); diff --git a/apps/openmw/mwgui/videowidget.cpp b/apps/openmw/mwgui/videowidget.cpp index 566c7cadb..cfd837a95 100644 --- a/apps/openmw/mwgui/videowidget.cpp +++ b/apps/openmw/mwgui/videowidget.cpp @@ -4,17 +4,12 @@ namespace MWGui { VideoWidget::VideoWidget() - : mAllowSkipping(true) { - eventKeyButtonPressed += MyGUI::newDelegate(this, &VideoWidget::onKeyPressed); - setNeedKeyFocus(true); } -void VideoWidget::playVideo(const std::string &video, bool allowSkipping) +void VideoWidget::playVideo(const std::string &video) { - mAllowSkipping = allowSkipping; - mPlayer.playVideo(video); setImageTexture(mPlayer.getTextureName()); @@ -30,16 +25,15 @@ int VideoWidget::getVideoHeight() return mPlayer.getVideoHeight(); } -void VideoWidget::onKeyPressed(MyGUI::Widget *_sender, MyGUI::KeyCode _key, MyGUI::Char _char) -{ - if (_key == MyGUI::KeyCode::Escape && mAllowSkipping) - mPlayer.stopVideo(); -} - bool VideoWidget::update() { mPlayer.update(); return mPlayer.isPlaying(); } +void VideoWidget::stop() +{ + mPlayer.close(); +} + } diff --git a/apps/openmw/mwgui/videowidget.hpp b/apps/openmw/mwgui/videowidget.hpp index 16a71d367..ad3d4d5e3 100644 --- a/apps/openmw/mwgui/videowidget.hpp +++ b/apps/openmw/mwgui/videowidget.hpp @@ -9,7 +9,7 @@ namespace MWGui { /** - * Widget that plays a video. Can be skipped by pressing Esc. + * Widget that plays a video. */ class VideoWidget : public MyGUI::ImageBox { @@ -18,7 +18,7 @@ namespace MWGui VideoWidget(); - void playVideo (const std::string& video, bool allowSkipping); + void playVideo (const std::string& video); int getVideoWidth(); int getVideoHeight(); @@ -26,12 +26,11 @@ namespace MWGui /// @return Is the video still playing? bool update(); - private: - bool mAllowSkipping; + /// Stop video and free resources (done automatically on destruction) + void stop(); + private: MWRender::VideoPlayer mPlayer; - - void onKeyPressed(MyGUI::Widget *_sender, MyGUI::KeyCode _key, MyGUI::Char _char); }; } diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 0e4ac22a6..b49bfbfa6 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -64,6 +64,7 @@ #include "fontloader.hpp" #include "videowidget.hpp" #include "backgroundimage.hpp" +#include "itemwidget.hpp" namespace MWGui { @@ -166,6 +167,7 @@ namespace MWGui MyGUI::FactoryManager::getInstance().registerFactory("Widget"); BookPage::registerMyGUIComponents (); ItemView::registerComponents(); + ItemWidget::registerComponents(); MyGUI::FactoryManager::getInstance().registerFactory("Controller"); @@ -200,8 +202,12 @@ namespace MWGui MyGUI::Align::Default, "Overlay"); mVideoBackground->setImageTexture("black.png"); mVideoBackground->setVisible(false); + mVideoBackground->setNeedMouseFocus(true); + mVideoBackground->setNeedKeyFocus(true); mVideoWidget = mVideoBackground->createWidgetReal("ImageBox", 0,0,1,1, MyGUI::Align::Default); + mVideoWidget->setNeedMouseFocus(true); + mVideoWidget->setNeedKeyFocus(true); } void WindowManager::initUI() @@ -261,7 +267,7 @@ namespace MWGui mCompanionWindow = new CompanionWindow(mDragAndDrop, mMessageBoxManager); trackWindow(mCompanionWindow, "companion"); - mInputBlocker = mGui->createWidget("",0,0,w,h,MyGUI::Align::Default,"Windows",""); + mInputBlocker = mGui->createWidget("",0,0,w,h,MyGUI::Align::Default,"Windows"); mHud->setVisible(mHudEnabled); @@ -1557,7 +1563,15 @@ namespace MWGui void WindowManager::playVideo(const std::string &name, bool allowSkipping) { - mVideoWidget->playVideo("video\\" + name, allowSkipping); + mVideoWidget->playVideo("video\\" + name); + + mVideoWidget->eventKeyButtonPressed.clear(); + mVideoBackground->eventKeyButtonPressed.clear(); + if (allowSkipping) + { + mVideoWidget->eventKeyButtonPressed += MyGUI::newDelegate(this, &WindowManager::onVideoKeyPressed); + mVideoBackground->eventKeyButtonPressed += MyGUI::newDelegate(this, &WindowManager::onVideoKeyPressed); + } // Turn off all rendering except for the GUI mRendering->getScene()->clearSpecialCaseRenderQueues(); @@ -1579,12 +1593,13 @@ namespace MWGui bool cursorWasVisible = mCursorVisible; setCursorVisible(false); - while (mVideoWidget->update()) + while (mVideoWidget->update() && !MWBase::Environment::get().getStateManager()->hasQuitRequest()) { MWBase::Environment::get().getInputManager()->update(0, true, false); mRendering->getWindow()->update(); } + mVideoWidget->stop(); setCursorVisible(cursorWasVisible); @@ -1625,4 +1640,33 @@ namespace MWGui if(input == mCurrentModals.top()) mCurrentModals.pop(); } + + void WindowManager::onVideoKeyPressed(MyGUI::Widget *_sender, MyGUI::KeyCode _key, MyGUI::Char _char) + { + if (_key == MyGUI::KeyCode::Escape) + mVideoWidget->stop(); + } + + void WindowManager::pinWindow(GuiWindow window) + { + switch (window) + { + case GW_Inventory: + mInventoryWindow->setPinned(true); + break; + case GW_Map: + mMap->setPinned(true); + break; + case GW_Magic: + mSpellWindow->setPinned(true); + break; + case GW_Stats: + mStatsWindow->setPinned(true); + break; + default: + break; + } + + updateVisible(); + } } diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index b1dbf3a24..dd41635ad 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -12,6 +12,9 @@ #include "../mwbase/windowmanager.hpp" +#include +#include + namespace MyGUI { class Gui; @@ -316,6 +319,8 @@ namespace MWGui \param input Pointer to the current modal, to ensure proper modal is removed **/ virtual void removeCurrentModal(WindowModal* input); + virtual void pinWindow (MWGui::GuiWindow window); + private: bool mConsoleOnlyScripts; @@ -424,6 +429,9 @@ namespace MWGui void onCursorChange(const std::string& name); void onKeyFocusChanged(MyGUI::Widget* widget); + // Key pressed while playing a video + void onVideoKeyPressed(MyGUI::Widget *_sender, MyGUI::KeyCode _key, MyGUI::Char _char); + void sizeVideo(int screenWidth, int screenHeight); }; } diff --git a/apps/openmw/mwgui/windowpinnablebase.cpp b/apps/openmw/mwgui/windowpinnablebase.cpp index 47364337c..919d315f2 100644 --- a/apps/openmw/mwgui/windowpinnablebase.cpp +++ b/apps/openmw/mwgui/windowpinnablebase.cpp @@ -25,6 +25,12 @@ namespace MWGui onPinToggled(); } + void WindowPinnableBase::setPinned(bool pinned) + { + if (pinned != mPinned) + onPinButtonClicked(mPinButton); + } + void WindowPinnableBase::setPinButtonVisible(bool visible) { mPinButton->setVisible(visible); diff --git a/apps/openmw/mwgui/windowpinnablebase.hpp b/apps/openmw/mwgui/windowpinnablebase.hpp index cd393f918..3aad60988 100644 --- a/apps/openmw/mwgui/windowpinnablebase.hpp +++ b/apps/openmw/mwgui/windowpinnablebase.hpp @@ -12,6 +12,7 @@ namespace MWGui public: WindowPinnableBase(const std::string& parLayout); bool pinned() { return mPinned; } + void setPinned (bool pinned); void setPinButtonVisible(bool visible); private: diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 943453a39..bb18b5aa7 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -117,7 +117,8 @@ namespace MWInput , mPreviewPOVDelay(0.f) , mTimeIdle(0.f) , mOverencumberedMessageDelay(0.f) - , mAlwaysRunActive(false) + , mAlwaysRunActive(Settings::Manager::getBool("always run", "Input")) + , mAttemptJump(false) , mControlsDisabled(false) { @@ -165,6 +166,21 @@ namespace MWInput delete mInputManager; } + void InputManager::setPlayerControlsEnabled(bool enabled) + { + int nPlayerChannels = 17; + int playerChannels[] = {A_Activate, A_AutoMove, A_AlwaysRun, A_ToggleWeapon, + A_ToggleSpell, A_Rest, A_QuickKey1, A_QuickKey2, + A_QuickKey3, A_QuickKey4, A_QuickKey5, A_QuickKey6, + A_QuickKey7, A_QuickKey8, A_QuickKey9, A_QuickKey10, + A_Use}; + + for(int i = 0; i < nPlayerChannels; i++) { + int pc = playerChannels[i]; + mInputBinder->getChannel(pc)->setEnabled(enabled); + } + } + void InputManager::channelChanged(ICS::Channel* channel, float currentValue, float previousValue) { if (mDragDrop) @@ -179,6 +195,11 @@ namespace MWInput mPlayer->getPlayer().getClass().getCreatureStats(mPlayer->getPlayer()).setAttackingOrSpell(currentValue); } + if (action == A_Jump) + { + mAttemptJump = (currentValue == 1.0 && previousValue == 0.0); + } + if (currentValue == 1) { // trigger action activated @@ -305,107 +326,107 @@ namespace MWInput } // Disable movement in Gui mode - if (MWBase::Environment::get().getWindowManager()->isGuiMode() - || MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_Running) - return; - - - // Configure player movement according to keyboard input. Actual movement will - // be done in the physics system. - if (mControlSwitch["playercontrols"]) + if (!(MWBase::Environment::get().getWindowManager()->isGuiMode() + || MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_Running)) { - bool triedToMove = false; - if (actionIsActive(A_MoveLeft)) - { - triedToMove = true; - mPlayer->setLeftRight (-1); - } - else if (actionIsActive(A_MoveRight)) + // Configure player movement according to keyboard input. Actual movement will + // be done in the physics system. + if (mControlSwitch["playercontrols"]) { - triedToMove = true; - mPlayer->setLeftRight (1); - } + bool triedToMove = false; + if (actionIsActive(A_MoveLeft)) + { + triedToMove = true; + mPlayer->setLeftRight (-1); + } + else if (actionIsActive(A_MoveRight)) + { + triedToMove = true; + mPlayer->setLeftRight (1); + } - if (actionIsActive(A_MoveForward)) - { - triedToMove = true; - mPlayer->setAutoMove (false); - mPlayer->setForwardBackward (1); - } - else if (actionIsActive(A_MoveBackward)) - { - triedToMove = true; - mPlayer->setAutoMove (false); - mPlayer->setForwardBackward (-1); - } + if (actionIsActive(A_MoveForward)) + { + triedToMove = true; + mPlayer->setAutoMove (false); + mPlayer->setForwardBackward (1); + } + else if (actionIsActive(A_MoveBackward)) + { + triedToMove = true; + mPlayer->setAutoMove (false); + mPlayer->setForwardBackward (-1); + } - else if(mPlayer->getAutoMove()) - { - triedToMove = true; - mPlayer->setForwardBackward (1); - } + else if(mPlayer->getAutoMove()) + { + triedToMove = true; + mPlayer->setForwardBackward (1); + } - mPlayer->setSneak(actionIsActive(A_Sneak)); + mPlayer->setSneak(actionIsActive(A_Sneak)); - if (actionIsActive(A_Jump) && mControlSwitch["playerjumping"]) - { - mPlayer->setUpDown (1); - triedToMove = true; - } + if (mAttemptJump && mControlSwitch["playerjumping"]) + { + mPlayer->setUpDown (1); + triedToMove = true; + } - if (mAlwaysRunActive) - mPlayer->setRunState(!actionIsActive(A_Run)); - else - mPlayer->setRunState(actionIsActive(A_Run)); + if (mAlwaysRunActive) + mPlayer->setRunState(!actionIsActive(A_Run)); + else + mPlayer->setRunState(actionIsActive(A_Run)); - // if player tried to start moving, but can't (due to being overencumbered), display a notification. - if (triedToMove) - { - MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); - mOverencumberedMessageDelay -= dt; - if (player.getClass().getEncumbrance(player) >= player.getClass().getCapacity(player)) + // if player tried to start moving, but can't (due to being overencumbered), display a notification. + if (triedToMove) { - mPlayer->setAutoMove (false); - if (mOverencumberedMessageDelay <= 0) + MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); + mOverencumberedMessageDelay -= dt; + if (player.getClass().getEncumbrance(player) >= player.getClass().getCapacity(player)) { - MWBase::Environment::get().getWindowManager ()->messageBox("#{sNotifyMessage59}"); - mOverencumberedMessageDelay = 1.0; + mPlayer->setAutoMove (false); + if (mOverencumberedMessageDelay <= 0) + { + MWBase::Environment::get().getWindowManager ()->messageBox("#{sNotifyMessage59}"); + mOverencumberedMessageDelay = 1.0; + } } } - } - - if (mControlSwitch["playerviewswitch"]) { - // work around preview mode toggle when pressing Alt+Tab - if (actionIsActive(A_TogglePOV) && !mInputManager->isModifierHeld(SDL_Keymod(KMOD_ALT))) { - if (mPreviewPOVDelay <= 0.5 && - (mPreviewPOVDelay += dt) > 0.5) - { - mPreviewPOVDelay = 1.f; - MWBase::Environment::get().getWorld()->togglePreviewMode(true); - } - } else { - //disable preview mode - MWBase::Environment::get().getWorld()->togglePreviewMode(false); - if (mPreviewPOVDelay > 0.f && mPreviewPOVDelay <= 0.5) { - MWBase::Environment::get().getWorld()->togglePOV(); + if (mControlSwitch["playerviewswitch"]) { + + // work around preview mode toggle when pressing Alt+Tab + if (actionIsActive(A_TogglePOV) && !mInputManager->isModifierHeld(SDL_Keymod(KMOD_ALT))) { + if (mPreviewPOVDelay <= 0.5 && + (mPreviewPOVDelay += dt) > 0.5) + { + mPreviewPOVDelay = 1.f; + MWBase::Environment::get().getWorld()->togglePreviewMode(true); + } + } else { + //disable preview mode + MWBase::Environment::get().getWorld()->togglePreviewMode(false); + if (mPreviewPOVDelay > 0.f && mPreviewPOVDelay <= 0.5) { + MWBase::Environment::get().getWorld()->togglePOV(); + } + mPreviewPOVDelay = 0.f; } - mPreviewPOVDelay = 0.f; } } + if (actionIsActive(A_MoveForward) || + actionIsActive(A_MoveBackward) || + actionIsActive(A_MoveLeft) || + actionIsActive(A_MoveRight) || + actionIsActive(A_Jump) || + actionIsActive(A_Sneak) || + actionIsActive(A_TogglePOV)) + { + resetIdleTime(); + } else { + updateIdleTime(dt); + } } - if (actionIsActive(A_MoveForward) || - actionIsActive(A_MoveBackward) || - actionIsActive(A_MoveLeft) || - actionIsActive(A_MoveRight) || - actionIsActive(A_Jump) || - actionIsActive(A_Sneak) || - actionIsActive(A_TogglePOV)) - { - resetIdleTime(); - } else { - updateIdleTime(dt); - } + mAttemptJump = false; // Can only jump on first frame input is on } void InputManager::setDragDrop(bool dragDrop) @@ -517,13 +538,15 @@ namespace MWInput } } - if (!mControlsDisabled) - mInputBinder->keyPressed (arg); - OIS::KeyCode kc = mInputManager->sdl2OISKeyCode(arg.keysym.sym); if (kc != OIS::KC_UNASSIGNED) - MyGUI::InputManager::getInstance().injectKeyPress(MyGUI::KeyCode::Enum(kc), 0); + { + bool guiFocus = MyGUI::InputManager::getInstance().injectKeyPress(MyGUI::KeyCode::Enum(kc), 0); + setPlayerControlsEnabled(!guiFocus); + } + if (!mControlsDisabled) + mInputBinder->keyPressed (arg); } void InputManager::textInput(const SDL_TextInputEvent &arg) @@ -536,36 +559,51 @@ namespace MWInput void InputManager::keyReleased(const SDL_KeyboardEvent &arg ) { - mInputBinder->keyReleased (arg); - OIS::KeyCode kc = mInputManager->sdl2OISKeyCode(arg.keysym.sym); - MyGUI::InputManager::getInstance().injectKeyRelease(MyGUI::KeyCode::Enum(kc)); + setPlayerControlsEnabled(!MyGUI::InputManager::getInstance().injectKeyRelease(MyGUI::KeyCode::Enum(kc))); + mInputBinder->keyReleased (arg); } void InputManager::mousePressed( const SDL_MouseButtonEvent &arg, Uint8 id ) { - mInputBinder->mousePressed (arg, id); - - if (id != SDL_BUTTON_LEFT && id != SDL_BUTTON_RIGHT) - return; // MyGUI has no use for these events + bool guiMode = false; - MyGUI::InputManager::getInstance().injectMousePress(mMouseX, mMouseY, sdlButtonToMyGUI(id)); - if (MyGUI::InputManager::getInstance ().getMouseFocusWidget () != 0) + if (id == SDL_BUTTON_LEFT || id == SDL_BUTTON_RIGHT) // MyGUI only uses these mouse events { - MyGUI::Button* b = MyGUI::InputManager::getInstance ().getMouseFocusWidget ()->castType(false); - if (b && b->getEnabled()) + guiMode = MWBase::Environment::get().getWindowManager()->isGuiMode(); + guiMode = MyGUI::InputManager::getInstance().injectMousePress(mMouseX, mMouseY, sdlButtonToMyGUI(id)) && guiMode; + if (MyGUI::InputManager::getInstance ().getMouseFocusWidget () != 0) { - MWBase::Environment::get().getSoundManager ()->playSound ("Menu Click", 1.f, 1.f); + MyGUI::Button* b = MyGUI::InputManager::getInstance ().getMouseFocusWidget ()->castType(false); + if (b && b->getEnabled()) + { + MWBase::Environment::get().getSoundManager ()->playSound ("Menu Click", 1.f, 1.f); + } } } + + setPlayerControlsEnabled(!guiMode); + mInputBinder->mousePressed (arg, id); + + } void InputManager::mouseReleased( const SDL_MouseButtonEvent &arg, Uint8 id ) - { - mInputBinder->mouseReleased (arg, id); + { + + if(mInputBinder->detectingBindingState()) + { + mInputBinder->mouseReleased (arg, id); + } else { + bool guiMode = MWBase::Environment::get().getWindowManager()->isGuiMode(); + guiMode = MyGUI::InputManager::getInstance().injectMouseRelease(mMouseX, mMouseY, sdlButtonToMyGUI(id)) && guiMode; - MyGUI::InputManager::getInstance().injectMouseRelease(mMouseX, mMouseY, sdlButtonToMyGUI(id)); + if(mInputBinder->detectingBindingState()) return; // don't allow same mouseup to bind as initiated bind + + setPlayerControlsEnabled(!guiMode); + mInputBinder->mouseReleased (arg, id); + } } void InputManager::mouseMoved(const SFO::MouseMotionEvent &arg ) @@ -769,8 +807,9 @@ namespace MWInput if (MyGUI::InputManager::getInstance ().isModalAny()) return; - // Toggle between game mode and journal mode - if(!MWBase::Environment::get().getWindowManager()->isGuiMode() && MWBase::Environment::get().getWindowManager ()->getJournalAllowed()) + if((!MWBase::Environment::get().getWindowManager()->isGuiMode() + || MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_Dialogue) + && MWBase::Environment::get().getWindowManager ()->getJournalAllowed()) { MWBase::Environment::get().getSoundManager()->playSound ("book open", 1.0, 1.0); MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Journal); @@ -779,7 +818,6 @@ namespace MWInput { MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); } - // .. but don't touch any other mode. } void InputManager::quickKey (int index) @@ -819,6 +857,8 @@ namespace MWInput { if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return; mAlwaysRunActive = !mAlwaysRunActive; + + Settings::Manager::setBool("always run", "Input", mAlwaysRunActive); } void InputManager::resetIdleTime() @@ -917,11 +957,6 @@ namespace MWInput mInputBinder->addMouseButtonBinding (control, defaultMouseButtonBindings[i], ICS::Control::INCREASE); } } - - // Printscreen key should not be allowed because it's captured by system screenshot function - // We check this explicitely here to fix up pre-0.26 config files. Can be removed after a few versions - if (mInputBinder->getKeyBinding(mInputBinder->getControl(A_Screenshot), ICS::Control::INCREASE) == SDLK_PRINTSCREEN) - mInputBinder->addKeyBinding(mInputBinder->getControl(A_Screenshot), SDLK_F12, ICS::Control::INCREASE); } std::string InputManager::getActionDescription (int action) diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index 3787a9c07..6bf1ad6b0 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -163,6 +163,7 @@ namespace MWInput int mMouseWheel; bool mUserFileExists; bool mAlwaysRunActive; + bool mAttemptJump; std::map mControlSwitch; @@ -173,6 +174,8 @@ namespace MWInput void resetIdleTime(); void updateIdleTime(float dt); + void setPlayerControlsEnabled(bool enabled); + private: void toggleMainMenu(); void toggleSpell(); diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 3d7e7e283..5727996d9 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -932,7 +932,7 @@ namespace MWMechanics for (std::list::iterator it = listGuards.begin(); it != listGuards.end(); ++it) { - engageCombat(iter->first, *it, false); + engageCombat(iter->first, *it, *it == player); } } @@ -1088,10 +1088,10 @@ namespace MWMechanics CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); - float healthHours = healthPerHour >= 0 + float healthHours = healthPerHour > 0 ? (stats.getHealth().getModified() - stats.getHealth().getCurrent()) / healthPerHour : 1.0f; - float magickaHours = magickaPerHour >= 0 + float magickaHours = magickaPerHour > 0 ? (stats.getMagicka().getModified() - stats.getMagicka().getCurrent()) / magickaPerHour : 1.0f; diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index 64b358b96..2e03122d5 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -395,7 +395,8 @@ int MWMechanics::Alchemy::addIngredient (const MWWorld::Ptr& ingredient) return -1; for (TIngredientsIterator iter (mIngredients.begin()); iter!=mIngredients.end(); ++iter) - if (!iter->isEmpty() && ingredient.get()==iter->get()) + if (!iter->isEmpty() && Misc::StringUtils::ciEqual(ingredient.getClass().getId(ingredient), + iter->getClass().getId(*iter))) return -1; mIngredients[slot] = ingredient; diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index c9da912dd..2d4bd8744 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -409,6 +409,13 @@ MWWorld::ContainerStoreIterator getActiveWeapon(CreatureStats &stats, MWWorld::I void CharacterController::playDeath(float startpoint, CharacterState death) { + if (mPtr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + { + // The first-person animations do not include death, so we need to + // force-switch to third person before playing the death animation. + MWBase::Environment::get().getWorld()->useDeathCamera(); + } + switch (death) { case CharState_SwimDeath: @@ -825,6 +832,8 @@ bool CharacterController::updateWeaponState() MWRender::Animation::Group_UpperBody, false, weapSpeed, mAttackType+" max attack", mAttackType+" min hit", 1.0f-complete, 0); + + complete = 0.f; mUpperBodyState = UpperCharState_MaxAttackToMinHit; } else if (mHitState == CharState_KnockDown) @@ -968,7 +977,8 @@ bool CharacterController::updateWeaponState() } //if playing combat animation and lowerbody is not busy switch to whole body animation - if((weaptype != WeapType_None || UpperCharState_UnEquipingWeap) && animPlaying) + if((weaptype != WeapType_None || mUpperBodyState == UpperCharState_UnEquipingWeap + || mUpperBodyState == UpperCharState_EquipingWeap) && animPlaying) { if( mMovementState != CharState_None || mJumpState != JumpState_None || diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 69c3c08f7..be62e8315 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -73,9 +73,16 @@ namespace MWMechanics return false; MWMechanics::CreatureStats& blockerStats = blocker.getClass().getCreatureStats(blocker); + + // Don't block when in spellcasting state (shield is equipped, but not visible) if (blockerStats.getDrawState() == DrawState_Spell) return false; + // Don't block when in hand-to-hand combat (shield is equipped, but not visible) + if (blockerStats.getDrawState() == DrawState_Weapon && + inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight) == inv.end()) + return false; + MWMechanics::CreatureStats& attackerStats = attacker.getClass().getCreatureStats(attacker); float blockTerm = blocker.getClass().getSkill(blocker, ESM::Skill::Block) + 0.2 * blockerStats.getAttribute(ESM::Attribute::Agility).getModified() diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 900ea72ca..f39a4b961 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -929,7 +929,10 @@ namespace MWMechanics else if (type == OT_Murder) arg = store.find("iCrimeKilling")->getInt(); else if (type == OT_Theft) + { arg *= store.find("fCrimeStealing")->getFloat(); + arg = std::max(1, arg); // Minimum bounty of 1, in case items with zero value are stolen + } MWBase::Environment::get().getWindowManager()->messageBox("#{sCrimeMessage}"); ptr.getClass().getNpcStats(ptr).setBounty(ptr.getClass().getNpcStats(ptr).getBounty() diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 9ed4cb6d3..dd900f79c 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -356,28 +356,40 @@ void Animation::addExtraLight(Ogre::SceneManager *sceneMgr, NifOgre::ObjectScene objlist->mControllers.push_back(Ogre::Controller(src, dest, func)); bool interior = !(mPtr.isInCell() && mPtr.getCell()->getCell()->isExterior()); - bool quadratic = fallback->getFallbackBool("LightAttenuation_OutQuadInLin") ? - !interior : fallback->getFallbackBool("LightAttenuation_UseQuadratic"); + + static bool outQuadInLin = fallback->getFallbackBool("LightAttenuation_OutQuadInLin"); + static bool useQuadratic = fallback->getFallbackBool("LightAttenuation_UseQuadratic"); + static float quadraticValue = fallback->getFallbackFloat("LightAttenuation_QuadraticValue"); + static float quadraticRadiusMult = fallback->getFallbackFloat("LightAttenuation_QuadraticRadiusMult"); + static bool useLinear = fallback->getFallbackBool("LightAttenuation_UseLinear"); + static float linearRadiusMult = fallback->getFallbackFloat("LightAttenuation_LinearRadiusMult"); + static float linearValue = fallback->getFallbackFloat("LightAttenuation_LinearValue"); + + bool quadratic = useQuadratic && (!outQuadInLin || !interior); + // with the standard 1 / (c + d*l + d*d*q) equation the attenuation factor never becomes zero, // so we ignore lights if their attenuation falls below this factor. const float threshold = 0.03; - if (!quadratic) + float quadraticAttenuation = 0; + float linearAttenuation = 0; + float activationRange = 0; + if (quadratic) { - float r = radius * fallback->getFallbackFloat("LightAttenuation_LinearRadiusMult"); - float attenuation = fallback->getFallbackFloat("LightAttenuation_LinearValue") / r; - float activationRange = 1.0f / (threshold * attenuation); - olight->setAttenuation(activationRange, 0, attenuation, 0); + float r = radius * quadraticRadiusMult; + quadraticAttenuation = quadraticValue / std::pow(r, 2); + activationRange = std::sqrt(1.0f / (threshold * quadraticAttenuation)); } - else + if (useLinear) { - float r = radius * fallback->getFallbackFloat("LightAttenuation_QuadraticRadiusMult"); - float attenuation = fallback->getFallbackFloat("LightAttenuation_QuadraticValue") / std::pow(r, 2); - float activationRange = std::sqrt(1.0f / (threshold * attenuation)); - olight->setAttenuation(activationRange, 0, 0, attenuation); + float r = radius * linearRadiusMult; + linearAttenuation = linearValue / r; + activationRange = std::max(activationRange, 1.0f / (threshold * linearAttenuation)); } + olight->setAttenuation(activationRange, 0, linearAttenuation, quadraticAttenuation); + // If there's an AttachLight bone, attach the light to that, otherwise put it in the center, if(objlist->mSkelBase && objlist->mSkelBase->getSkeleton()->hasBone("AttachLight")) objlist->mSkelBase->attachObjectToBone("AttachLight", olight); diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index 294264951..9e683cc15 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -140,11 +140,11 @@ namespace MWRender } } - void Camera::toggleViewMode() + void Camera::toggleViewMode(bool force) { // Changing the view will stop all playing animations, so if we are playing // anything important, queue the view change for later - if (!mAnimation->allowSwitchViewMode()) + if (!mAnimation->allowSwitchViewMode() && !force) { mViewModeToggleQueued = true; return; diff --git a/apps/openmw/mwrender/camera.hpp b/apps/openmw/mwrender/camera.hpp index 808f817cf..1e86bfb48 100644 --- a/apps/openmw/mwrender/camera.hpp +++ b/apps/openmw/mwrender/camera.hpp @@ -75,7 +75,8 @@ namespace MWRender /// Attach camera to object void attachTo(const MWWorld::Ptr &); - void toggleViewMode(); + /// @param Force view mode switch, even if currently not allowed by the animation. + void toggleViewMode(bool force=false); bool toggleVanityMode(bool enable); void allowVanityMode(bool allow); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 03ccde388..be2b262fc 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -308,7 +308,7 @@ void NpcAnimation::updateParts() ESM::PartReferenceType parts[] = { ESM::PRT_Groin, ESM::PRT_Skirt, ESM::PRT_RLeg, ESM::PRT_LLeg, ESM::PRT_RUpperarm, ESM::PRT_LUpperarm, ESM::PRT_RKnee, ESM::PRT_LKnee, - ESM::PRT_RForearm, ESM::PRT_LForearm, ESM::PRT_RPauldron, ESM::PRT_LPauldron + ESM::PRT_RForearm, ESM::PRT_LForearm }; size_t parts_size = sizeof(parts)/sizeof(parts[0]); for(size_t p = 0;p < parts_size;++p) @@ -351,8 +351,6 @@ void NpcAnimation::updateParts() // Remember body parts so we only have to search through the store once for each race/gender/viewmode combination static std::map< std::pair,std::vector > sRaceMapping; - static std::map , std::vector > sVampireMapping; - static const int Flag_Female = 1<<0; static const int Flag_FirstPerson = 1<<1; @@ -408,8 +406,6 @@ void NpcAnimation::updateParts() if (bodypart.mData.mType != ESM::BodyPart::MT_Skin) continue; - if (!mNpc->isMale() != (bodypart.mData.mFlags & ESM::BodyPart::BPF_Female)) - continue; if (!Misc::StringUtils::ciEqual(bodypart.mRace, mNpc->mRace)) continue; @@ -435,6 +431,20 @@ void NpcAnimation::updateParts() } continue; } + + if (!mNpc->isMale() != (bodypart.mData.mFlags & ESM::BodyPart::BPF_Female)) + { + // Allow opposite gender's parts as fallback if parts for our gender are 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; + } + BodyPartMapType::const_iterator bIt = sBodyPartMap.lower_bound(BodyPartMapType::key_type(bodypart.mData.mPart)); while(bIt != sBodyPartMap.end() && bIt->first == bodypart.mData.mPart) { diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index 7953a3117..d9e20e1f8 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -73,7 +73,7 @@ void Objects::insertBegin(const MWWorld::Ptr& ptr) ptr.getRefData().setBaseNode(insert); } -void Objects::insertModel(const MWWorld::Ptr &ptr, const std::string &mesh) +void Objects::insertModel(const MWWorld::Ptr &ptr, const std::string &mesh, bool batch) { insertBegin(ptr); @@ -99,7 +99,7 @@ void Objects::insertModel(const MWWorld::Ptr &ptr, const std::string &mesh) mBounds[ptr.getCell()] = Ogre::AxisAlignedBox::BOX_NULL; mBounds[ptr.getCell()].merge(bounds); - if(ptr.getTypeName() == typeid(ESM::Static).name() && + if(batch && Settings::Manager::getBool("use static geometry", "Objects") && anim->canBatch()) { diff --git a/apps/openmw/mwrender/objects.hpp b/apps/openmw/mwrender/objects.hpp index 665a1e657..02e974e2d 100644 --- a/apps/openmw/mwrender/objects.hpp +++ b/apps/openmw/mwrender/objects.hpp @@ -41,7 +41,7 @@ public: , mRootNode(NULL) {} ~Objects(){} - void insertModel(const MWWorld::Ptr& ptr, const std::string &model); + void insertModel(const MWWorld::Ptr& ptr, const std::string &model, bool batch=false); ObjectAnimation* getAnimation(const MWWorld::Ptr &ptr); diff --git a/apps/openmw/mwscript/dialogueextensions.cpp b/apps/openmw/mwscript/dialogueextensions.cpp index 9dde65ab2..45eeccf18 100644 --- a/apps/openmw/mwscript/dialogueextensions.cpp +++ b/apps/openmw/mwscript/dialogueextensions.cpp @@ -235,6 +235,18 @@ namespace MWScript } }; + template + class OpClearInfoActor : public Interpreter::Opcode0 + { + public: + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + MWBase::Environment::get().getDialogueManager()->clearInfoActor(ptr); + } + }; + void installOpcodes (Interpreter::Interpreter& interpreter) { @@ -256,6 +268,8 @@ namespace MWScript interpreter.installSegment5 (Compiler::Dialogue::opcodeSameFactionExplicit, new OpSameFaction); interpreter.installSegment5 (Compiler::Dialogue::opcodeModFactionReaction, new OpModFactionReaction); interpreter.installSegment5 (Compiler::Dialogue::opcodeGetFactionReaction, new OpGetFactionReaction); + interpreter.installSegment5 (Compiler::Dialogue::opcodeClearInfoActor, new OpClearInfoActor); + interpreter.installSegment5 (Compiler::Dialogue::opcodeClearInfoActorExplicit, new OpClearInfoActor); } } diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index 24b0b6f7a..1576bf0af 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -57,7 +57,8 @@ op 0x20028: RemoveSoulGem, explicit reference op 0x20029: PCRaiseRank, explicit reference op 0x2002a: PCLowerRank, explicit reference op 0x2002b: PCJoinFaction, explicit reference -opcodes 0x2002c-0x3ffff unused +op 0x2002c: MenuTest +opcodes 0x2002d-0x3ffff unused Segment 4: (not implemented yet) @@ -393,5 +394,7 @@ op 0x2000241: onKnockoutExplicit op 0x2000242: ModFactionReaction op 0x2000243: GetFactionReaction op 0x2000244: Activate, explicit +op 0x2000245: ClearInfoActor +op 0x2000246: ClearInfoActor, explicit -opcodes 0x2000245-0x3ffffff unused +opcodes 0x2000247-0x3ffffff unused diff --git a/apps/openmw/mwscript/guiextensions.cpp b/apps/openmw/mwscript/guiextensions.cpp index be241a564..333be5be6 100644 --- a/apps/openmw/mwscript/guiextensions.cpp +++ b/apps/openmw/mwscript/guiextensions.cpp @@ -162,6 +162,47 @@ namespace MWScript } }; + class OpMenuTest : public Interpreter::Opcode1 + { + public: + + virtual void execute (Interpreter::Runtime& runtime, unsigned int arg0) + { + int arg=0; + if(arg0>0) + { + arg = runtime[0].mInteger; + runtime.pop(); + } + + + if (arg == 0) + { + MWGui::GuiMode modes[] = { MWGui::GM_Inventory, MWGui::GM_Container }; + + for (int i=0; i<2; ++i) + { + if (MWBase::Environment::get().getWindowManager()->containsMode(modes[i])) + MWBase::Environment::get().getWindowManager()->removeGuiMode(modes[i]); + } + } + else + { + MWGui::GuiWindow gw = MWGui::GW_None; + if (arg == 3) + gw = MWGui::GW_Stats; + if (arg == 4) + gw = MWGui::GW_Inventory; + if (arg == 5) + gw = MWGui::GW_Magic; + if (arg == 6) + gw = MWGui::GW_Map; + + MWBase::Environment::get().getWindowManager()->pinWindow(gw); + } + } + }; + void installOpcodes (Interpreter::Interpreter& interpreter) { @@ -200,6 +241,7 @@ namespace MWScript interpreter.installSegment5 (Compiler::Gui::opcodeShowMap, new OpShowMap); interpreter.installSegment5 (Compiler::Gui::opcodeFillMap, new OpFillMap); + interpreter.installSegment3 (Compiler::Gui::opcodeMenuTest, new OpMenuTest); } } } diff --git a/apps/openmw/mwstate/character.cpp b/apps/openmw/mwstate/character.cpp index 5fe80ce0c..6f569f078 100644 --- a/apps/openmw/mwstate/character.cpp +++ b/apps/openmw/mwstate/character.cpp @@ -54,9 +54,28 @@ void MWState::Character::addSlot (const ESM::SavedGame& profile) Slot slot; std::ostringstream stream; - stream << mNext++; + + // The profile description is user-supplied, so we need to escape the path + for (std::string::const_iterator it = profile.mDescription.begin(); it != profile.mDescription.end(); ++it) + { + if (std::isalnum(*it)) // Ignores multibyte characters and non alphanumeric characters + stream << *it; + else + stream << "_"; + } slot.mPath = mPath / stream.str(); + + // Append an index if necessary to ensure a unique file + int i=0; + while (boost::filesystem::exists(slot.mPath)) + { + std::ostringstream test; + test << stream.str(); + test << " - " << ++i; + slot.mPath = mPath / test.str(); + } + slot.mProfile = profile; slot.mTimeStamp = std::time (0); @@ -64,7 +83,7 @@ void MWState::Character::addSlot (const ESM::SavedGame& profile) } MWState::Character::Character (const boost::filesystem::path& saves, const std::string& game) -: mPath (saves), mNext (0) +: mPath (saves) { if (!boost::filesystem::is_directory (mPath)) { @@ -82,13 +101,6 @@ MWState::Character::Character (const boost::filesystem::path& saves, const std:: addSlot (slotPath, game); } catch (...) {} // ignoring bad saved game files for now - - std::istringstream stream (slotPath.filename().string()); - - int index = 0; - - if ((stream >> index) && index>=mNext) - mNext = index+1; } std::sort (mSlots.begin(), mSlots.end()); diff --git a/apps/openmw/mwstate/character.hpp b/apps/openmw/mwstate/character.hpp index 874533289..4703f0cca 100644 --- a/apps/openmw/mwstate/character.hpp +++ b/apps/openmw/mwstate/character.hpp @@ -26,7 +26,6 @@ namespace MWState boost::filesystem::path mPath; std::vector mSlots; - int mNext; void addSlot (const boost::filesystem::path& path, const std::string& game); diff --git a/apps/openmw/mwstate/charactermanager.cpp b/apps/openmw/mwstate/charactermanager.cpp index d773904db..91d728ae0 100644 --- a/apps/openmw/mwstate/charactermanager.cpp +++ b/apps/openmw/mwstate/charactermanager.cpp @@ -8,7 +8,7 @@ MWState::CharacterManager::CharacterManager (const boost::filesystem::path& saves, const std::string& game) -: mPath (saves), mNext (0), mCurrent (0), mGame (game) +: mPath (saves), mCurrent (0), mGame (game) { if (!boost::filesystem::is_directory (mPath)) { @@ -28,21 +28,14 @@ MWState::CharacterManager::CharacterManager (const boost::filesystem::path& save if (character.begin()!=character.end()) mCharacters.push_back (character); } - - std::istringstream stream (characterDir.filename().string()); - - int index = 0; - - if ((stream >> index) && index>=mNext) - mNext = index+1; } } } -MWState::Character *MWState::CharacterManager::getCurrentCharacter (bool create) +MWState::Character *MWState::CharacterManager::getCurrentCharacter (bool create, const std::string& name) { if (!mCurrent && create) - createCharacter(); + createCharacter(name); return mCurrent; } @@ -63,13 +56,31 @@ void MWState::CharacterManager::deleteSlot(const MWState::Character *character, } } -void MWState::CharacterManager::createCharacter() +void MWState::CharacterManager::createCharacter(const std::string& name) { std::ostringstream stream; - stream << mNext++; + + // The character name is user-supplied, so we need to escape the path + for (std::string::const_iterator it = name.begin(); it != name.end(); ++it) + { + if (std::isalnum(*it)) // Ignores multibyte characters and non alphanumeric characters + stream << *it; + else + stream << "_"; + } boost::filesystem::path path = mPath / stream.str(); + // Append an index if necessary to ensure a unique directory + int i=0; + while (boost::filesystem::exists(path)) + { + std::ostringstream test; + test << stream.str(); + test << " - " << ++i; + path = mPath / test.str(); + } + mCharacters.push_back (Character (path, mGame)); mCurrent = &mCharacters.back(); diff --git a/apps/openmw/mwstate/charactermanager.hpp b/apps/openmw/mwstate/charactermanager.hpp index adf9d2ef4..c44c10b5a 100644 --- a/apps/openmw/mwstate/charactermanager.hpp +++ b/apps/openmw/mwstate/charactermanager.hpp @@ -10,7 +10,6 @@ namespace MWState class CharacterManager { boost::filesystem::path mPath; - int mNext; // Uses std::list, so that mCurrent stays valid when characters are deleted std::list mCharacters; @@ -32,13 +31,15 @@ namespace MWState CharacterManager (const boost::filesystem::path& saves, const std::string& game); - Character *getCurrentCharacter (bool create = true); + Character *getCurrentCharacter (bool create, const std::string& name); ///< \param create Create a new character, if there is no current character. + /// \param name The character name to use in case a new character is created. void deleteSlot(const MWState::Character *character, const MWState::Slot *slot); - void createCharacter(); + void createCharacter(const std::string& name); ///< Create new character within saved game management + /// \param name Name for the character (does not need to be unique) void setCurrentCharacter (const Character *character); diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 29b5318d7..68cb91eb9 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -144,7 +144,6 @@ void MWState::StateManager::newGame (bool bypass) void MWState::StateManager::endGame() { mState = State_Ended; - MWBase::Environment::get().getWorld()->useDeathCamera(); } void MWState::StateManager::saveGame (const std::string& description, const Slot *slot) @@ -185,9 +184,9 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot encoded->read(&profile.mScreenshot[0], encoded->size()); if (!slot) - slot = mCharacterManager.getCurrentCharacter()->createSlot (profile); + slot = getCurrentCharacter()->createSlot (profile); else - slot = mCharacterManager.getCurrentCharacter()->updateSlot (slot, profile); + slot = getCurrentCharacter()->updateSlot (slot, profile); boost::filesystem::ofstream stream (slot->mPath, std::ios::binary); @@ -234,6 +233,9 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot writer.close(); + if (stream.fail()) + throw std::runtime_error("Write operation failed"); + Settings::Manager::setString ("character", "Saves", slot->mPath.parent_path().filename().string()); } @@ -247,6 +249,10 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot std::vector buttons; buttons.push_back("#{sOk}"); MWBase::Environment::get().getWindowManager()->messageBox(error.str(), buttons); + + // If no file was written, clean up the slot + if (slot && !boost::filesystem::exists(slot->mPath)) + getCurrentCharacter()->deleteSlot(slot); } } @@ -413,7 +419,10 @@ void MWState::StateManager::deleteGame(const MWState::Character *character, cons MWState::Character *MWState::StateManager::getCurrentCharacter (bool create) { - return mCharacterManager.getCurrentCharacter (create); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + std::string name = player.getClass().getName(player); + + return mCharacterManager.getCurrentCharacter (create, name); } MWState::StateManager::CharacterIterator MWState::StateManager::characterBegin() @@ -434,11 +443,12 @@ void MWState::StateManager::update (float duration) if (mAskLoadRecent) { int iButton = MWBase::Environment::get().getWindowManager()->readPressedButton(); - if(iButton==0) + MWState::Character *curCharacter = getCurrentCharacter(false); + if(iButton==0 && curCharacter) { mAskLoadRecent = false; //Load last saved game for current character - MWState::Character *curCharacter = getCurrentCharacter(); + MWState::Slot lastSave = *curCharacter->begin(); loadGame(curCharacter, &lastSave); } diff --git a/apps/openmw/mwworld/actionequip.cpp b/apps/openmw/mwworld/actionequip.cpp index 05677cdc7..50da1e5e5 100644 --- a/apps/openmw/mwworld/actionequip.cpp +++ b/apps/openmw/mwworld/actionequip.cpp @@ -41,6 +41,8 @@ namespace MWWorld // slots that this item can be equipped in std::pair, bool> slots_ = getTarget().getClass().getEquipmentSlots(getTarget()); + if (slots_.first.empty()) + return; // retrieve ContainerStoreIterator to the item MWWorld::ContainerStoreIterator it = invStore.begin(); @@ -55,25 +57,32 @@ namespace MWWorld assert(it != invStore.end()); // equip the item in the first free slot - for (std::vector::const_iterator slot=slots_.first.begin(); - slot!=slots_.first.end(); ++slot) + std::vector::const_iterator slot=slots_.first.begin(); + for (;slot!=slots_.first.end(); ++slot) { // if the item is equipped already, nothing to do if (invStore.getSlot(*slot) == it) return; - // if all slots are occupied, replace the last slot - if (slot == --slots_.first.end()) + if (invStore.getSlot(*slot) == invStore.end()) { + // slot is not occupied invStore.equip(*slot, it, actor); break; } + } - if (invStore.getSlot(*slot) == invStore.end()) + // all slots are occupied -> cycle + // move all slots one towards begin(), then equip the item in the slot that is now free + if (slot == slots_.first.end()) + { + for (slot=slots_.first.begin();slot!=slots_.first.end(); ++slot) { - // slot is not occupied - invStore.equip(*slot, it, actor); - break; + invStore.unequipSlot(*slot, actor); + if (slot+1 != slots_.first.end()) + invStore.equip(*slot, invStore.getSlot(*(slot+1)), actor); + else + invStore.equip(*slot, it, actor); } } } diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index cd6cc4a16..12831e7dc 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -99,6 +99,13 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) } it->second->load(esm, id); + // DELE can also occur after the usual subrecords + if (esm.isNextSub("DELE")) { + esm.skipRecord(); + it->second->eraseStatic(id); + continue; + } + if (n.val==ESM::REC_DIAL) { dialogue = const_cast(mDialogs.find(id)); } else { diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index e93d9e640..0f6fdec19 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -193,6 +193,11 @@ namespace MWWorld const ESM::Position &refpos = ptr.getRefData().getPosition(); Ogre::Vector3 position(refpos.pos); + // Early-out for totally static creatures + // (Not sure if gravity should still apply?) + if (!ptr.getClass().canWalk(ptr) && !isFlying && !ptr.getClass().canSwim(ptr)) + return position; + /* Anything to collide with? */ OEngine::Physic::PhysicActor *physicActor = engine->getCharacter(ptr.getRefData().getHandle()); if(!physicActor || !physicActor->getCollisionMode()) @@ -299,17 +304,33 @@ namespace MWWorld continue; // velocity updated, calculate nextpos again } - // trace to where character would go if there were no obstructions - tracer.doTrace(colobj, newPosition, nextpos, engine); + if(!newPosition.positionCloses(nextpos, 0.00000001)) + { + // trace to where character would go if there were no obstructions + tracer.doTrace(colobj, newPosition, nextpos, engine); - // check for obstructions - if(tracer.mFraction >= 1.0f) + // check for obstructions + if(tracer.mFraction >= 1.0f) + { + newPosition = tracer.mEndPos; // ok to move, so set newPosition + remainingTime *= (1.0f-tracer.mFraction); // FIXME: remainingTime is no longer used so don't set it? + break; + } + } + else { - newPosition = tracer.mEndPos; // ok to move, so set newPosition + // The current position and next position are nearly the same, so just exit. + // Note: Bullet can trigger an assert in debug modes if the positions + // are the same, since that causes it to attempt to normalize a zero + // length vector (which can also happen with nearly identical vectors, since + // precision can be lost due to any math Bullet does internally). Since we + // aren't performing any collision detection, we want to reject the next + // position, so that we don't slowly move inside another object. remainingTime *= (1.0f-tracer.mFraction); // FIXME: remainingTime is no longer used so don't set it? break; } + Ogre::Vector3 oldPosition = newPosition; // We hit something. Try to step up onto it. (NOTE: stepMove does not allow stepping over) // NOTE: stepMove modifies newPosition if successful @@ -492,7 +513,7 @@ namespace MWWorld return std::make_pair(true, ray.getPoint(len * test.second)); } - std::pair PhysicsSystem::castRay(float mouseX, float mouseY, Ogre::Vector3* normal) + std::pair PhysicsSystem::castRay(float mouseX, float mouseY, Ogre::Vector3* normal, std::string* hit) { Ogre::Ray ray = mRender.getCamera()->getCameraToViewportRay( mouseX, @@ -510,6 +531,8 @@ namespace MWWorld return std::make_pair(false, Ogre::Vector3()); else { + if (hit != NULL) + *hit = result.first; return std::make_pair(true, ray.getPoint(200*result.second)); /// \todo make this distance (ray length) configurable } } diff --git a/apps/openmw/mwworld/physicssystem.hpp b/apps/openmw/mwworld/physicssystem.hpp index 899d7144d..c590b40c8 100644 --- a/apps/openmw/mwworld/physicssystem.hpp +++ b/apps/openmw/mwworld/physicssystem.hpp @@ -70,9 +70,10 @@ namespace MWWorld std::pair castRay(const Ogre::Vector3 &orig, const Ogre::Vector3 &dir, float len); - std::pair castRay(float mouseX, float mouseY, Ogre::Vector3* normal = NULL); + std::pair castRay(float mouseX, float mouseY, Ogre::Vector3* normal = NULL, std::string* hit = NULL); ///< cast ray from the mouse, return true if it hit something and the first result /// @param normal if non-NULL, the hit normal will be written there (if there is a hit) + /// @param hit if non-NULL, the string handle of the hit object will be written there (if there is a hit) OEngine::Physic::PhysicEngine* getEngine(); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index dea803c87..bf752734f 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -432,7 +432,7 @@ namespace MWWorld mRendering->getCamera()->toggleVanityMode(false); } if(mRendering->getCamera()->isFirstPerson()) - togglePOV(); + mRendering->getCamera()->toggleViewMode(true); } MWWorld::Player& World::getPlayer() @@ -1624,12 +1624,20 @@ namespace MWWorld bool World::canPlaceObject(float cursorX, float cursorY) { Ogre::Vector3 normal(0,0,0); - std::pair result = mPhysics->castRay(cursorX, cursorY, &normal); + std::string handle; + std::pair result = mPhysics->castRay(cursorX, cursorY, &normal, &handle); if (result.first) { // check if the wanted position is on a flat surface, and not e.g. against a vertical wall - return (normal.angleBetween(Ogre::Vector3(0.f,0.f,1.f)).valueDegrees() < 30); + if (normal.angleBetween(Ogre::Vector3(0.f,0.f,1.f)).valueDegrees() >= 30) + return false; + + MWWorld::Ptr hitObject = searchPtrViaHandle(handle); + if (!hitObject.isEmpty() && hitObject.getClass().isActor()) + return false; + + return true; } else return false; @@ -1695,14 +1703,15 @@ namespace MWWorld Ogre::Vector3 orig = Ogre::Vector3(pos.pos); + orig.z += 20; Ogre::Vector3 dir = Ogre::Vector3(0, 0, -1); - float len = (pos.pos[2] >= 0) ? pos.pos[2] : -pos.pos[2]; - len += 100.0; + float len = 100.0; std::pair hit = mPhysics->castRay(orig, dir, len); - pos.pos[2] = hit.second.z; + if (hit.first) + pos.pos[2] = hit.second.z; // copy the object and set its count int origCount = object.getRefData().getCount(); @@ -1851,7 +1860,12 @@ namespace MWWorld if (!mPlayer) mPlayer = new MWWorld::Player(player, *this); else + { + // Remove the old CharacterController + MWBase::Environment::get().getMechanicsManager()->remove(getPlayerPtr()); + mPlayer->set(player); + } Ptr ptr = mPlayer->getPlayer(); mRendering->setupPlayer(ptr); diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index 0f726a52d..b9e36db80 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -181,6 +181,7 @@ namespace Compiler opcodeSameFactionExplicit); extensions.registerInstruction("modfactionreaction", "ccl", opcodeModFactionReaction); extensions.registerFunction("getfactionreaction", 'l', "ccl", opcodeGetFactionReaction); + extensions.registerInstruction("clearinfoactor", "", opcodeClearInfoActor, opcodeClearInfoActorExplicit); } } @@ -215,6 +216,7 @@ namespace Compiler extensions.registerInstruction ("showmap", "S", opcodeShowMap); extensions.registerInstruction ("fillmap", "", opcodeFillMap); + extensions.registerInstruction ("menutest", "/l", opcodeMenuTest); } } diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp index 8796c53c5..14f4db060 100644 --- a/components/compiler/opcodes.hpp +++ b/components/compiler/opcodes.hpp @@ -154,6 +154,8 @@ namespace Compiler const int opcodeSameFactionExplicit = 0x20001b6; const int opcodeModFactionReaction = 0x2000242; const int opcodeGetFactionReaction = 0x2000243; + const int opcodeClearInfoActor = 0x2000245; + const int opcodeClearInfoActorExplicit = 0x2000246; } namespace Gui @@ -175,6 +177,7 @@ namespace Compiler const int opcodeToggleFullHelp = 0x2000151; const int opcodeShowMap = 0x20001a0; const int opcodeFillMap = 0x20001a1; + const int opcodeMenuTest = 0x2002c; } namespace Misc diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 0d274474c..f5bc2369a 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -239,7 +239,7 @@ bool ContentSelectorModel::ContentModel::setData(const QModelIndex &index, const return false; EsmFile *file = item(index.row()); - QString fileName = file->filePath(); + QString fileName = file->fileName(); bool success = false; switch(role) @@ -266,7 +266,7 @@ bool ContentSelectorModel::ContentModel::setData(const QModelIndex &index, const if (success) { - success = setCheckState(fileName, value.toBool()); + success = setCheckState(file->filePath(), value.toBool()); emit dataChanged(index, index); } } @@ -277,19 +277,19 @@ bool ContentSelectorModel::ContentModel::setData(const QModelIndex &index, const int checkValue = value.toInt(); bool success = false; bool setState = false; - if ((checkValue==Qt::Checked) && !isChecked(fileName)) + if ((checkValue==Qt::Checked) && !isChecked(file->filePath())) { setState = true; success = true; } - else if ((checkValue == Qt::Checked) && isChecked (fileName)) + else if ((checkValue == Qt::Checked) && isChecked (file->filePath())) setState = true; else if (checkValue == Qt::Unchecked) setState = true; if (setState) { - setCheckState(fileName, success); + setCheckState(file->filePath(), success); emit dataChanged(index, index); } @@ -517,10 +517,10 @@ void ContentSelectorModel::ContentModel::sortFiles() } } -bool ContentSelectorModel::ContentModel::isChecked(const QString& name) const +bool ContentSelectorModel::ContentModel::isChecked(const QString& filepath) const { - if (mCheckStates.contains(name)) - return (mCheckStates[name] == Qt::Checked); + if (mCheckStates.contains(filepath)) + return (mCheckStates[filepath] == Qt::Checked); return false; } @@ -543,12 +543,12 @@ void ContentSelectorModel::ContentModel::refreshModel() emit dataChanged (index(0,0), index(rowCount()-1,0)); } -bool ContentSelectorModel::ContentModel::setCheckState(const QString &name, bool checkState) +bool ContentSelectorModel::ContentModel::setCheckState(const QString &filepath, bool checkState) { - if (name.isEmpty()) + if (filepath.isEmpty()) return false; - const EsmFile *file = item(name); + const EsmFile *file = item(filepath); if (!file) return false; @@ -558,8 +558,8 @@ bool ContentSelectorModel::ContentModel::setCheckState(const QString &name, bool if (checkState) state = Qt::Checked; - mCheckStates[name] = state; - emit dataChanged(indexFromItem(item(name)), indexFromItem(item(name))); + mCheckStates[filepath] = state; + emit dataChanged(indexFromItem(item(filepath)), indexFromItem(item(filepath))); if (file->isGameFile()) refreshModel(); @@ -586,7 +586,10 @@ bool ContentSelectorModel::ContentModel::setCheckState(const QString &name, bool { foreach (const EsmFile *downstreamFile, mFiles) { - if (downstreamFile->gameFiles().contains(name)) + QFileInfo fileInfo(filepath); + QString filename = fileInfo.fileName(); + + if (downstreamFile->gameFiles().contains(filename)) { if (mCheckStates.contains(downstreamFile->filePath())) mCheckStates[downstreamFile->filePath()] = Qt::Unchecked; diff --git a/components/contentselector/model/contentmodel.hpp b/components/contentselector/model/contentmodel.hpp index 8c8c2124b..6d147bce9 100644 --- a/components/contentselector/model/contentmodel.hpp +++ b/components/contentselector/model/contentmodel.hpp @@ -45,8 +45,8 @@ namespace ContentSelectorModel const EsmFile *item(const QString &name) const; bool isEnabled (QModelIndex index) const; - bool isChecked(const QString &name) const; - bool setCheckState(const QString &name, bool isChecked); + bool isChecked(const QString &filepath) const; + bool setCheckState(const QString &filepath, bool isChecked); void setCheckStates (const QStringList &fileList, bool isChecked); ContentFileList checkedItems() const; void uncheckAll(); diff --git a/components/contentselector/model/esmfile.hpp b/components/contentselector/model/esmfile.hpp index ca24b52d1..7e1edcaba 100644 --- a/components/contentselector/model/esmfile.hpp +++ b/components/contentselector/model/esmfile.hpp @@ -53,6 +53,8 @@ namespace ContentSelectorModel inline QDateTime modified() const { return mModified; } inline float format() const { return mFormat; } inline QString filePath() const { return mPath; } + + /// @note Contains file names, not paths. inline const QStringList &gameFiles() const { return mGameFiles; } inline QString description() const { return mDescription; } inline QString toolTip() const { return sToolTip.arg(mAuthor) diff --git a/components/esm/loadalch.cpp b/components/esm/loadalch.cpp index f6bfc6a11..aac88482f 100644 --- a/components/esm/loadalch.cpp +++ b/components/esm/loadalch.cpp @@ -10,7 +10,7 @@ namespace ESM void Potion::load(ESMReader &esm) { - mModel = esm.getHNString("MODL"); + mModel = esm.getHNOString("MODL"); mIcon = esm.getHNOString("TEXT"); // not ITEX here for some reason mScript = esm.getHNOString("SCRI"); mName = esm.getHNOString("FNAM"); diff --git a/components/esm/loadstat.cpp b/components/esm/loadstat.cpp index a71f22dc2..53d1b4bb5 100644 --- a/components/esm/loadstat.cpp +++ b/components/esm/loadstat.cpp @@ -10,6 +10,7 @@ namespace ESM void Static::load(ESMReader &esm) { + mPersistent = esm.getRecordFlags() & 0x0400; mModel = esm.getHNString("MODL"); } void Static::save(ESMWriter &esm) const diff --git a/components/esm/loadstat.hpp b/components/esm/loadstat.hpp index d912d1058..45b05136a 100644 --- a/components/esm/loadstat.hpp +++ b/components/esm/loadstat.hpp @@ -26,6 +26,8 @@ struct Static std::string mId, mModel; + bool mPersistent; + void load(ESMReader &esm); void save(ESMWriter &esm) const; diff --git a/components/nif/data.hpp b/components/nif/data.hpp index f1f34184b..e94388768 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -413,12 +413,25 @@ struct NiMorphData : public Record struct NiKeyframeData : public Record { QuaternionKeyList mRotations; + //\FIXME mXYZ_Keys are read, but not used. + FloatKeyList mXYZ_Keys; Vector3KeyList mTranslations; FloatKeyList mScales; void read(NIFStream *nif) { mRotations.read(nif); + if(mRotations.mInterpolationType == mRotations.sXYZInterpolation) + { + //Chomp unused float + nif->getFloat(); + for(size_t i=0;i<3;++i) + { + //Read concatenates items together. + mXYZ_Keys.read(nif,true); + } + nif->file->warn("XYZ_ROTATION_KEY read, but not used!"); + } mTranslations.read(nif); mScales.read(nif); } diff --git a/components/nif/niffile.hpp b/components/nif/niffile.hpp index 29fa3951e..daec80ea1 100644 --- a/components/nif/niffile.hpp +++ b/components/nif/niffile.hpp @@ -113,24 +113,24 @@ public: }; /// Get a given record - Record *getRecord(size_t index) + Record *getRecord(size_t index) const { Record *res = records.at(index); assert(res != NULL); return res; } /// Number of records - size_t numRecords() { return records.size(); } + size_t numRecords() const { return records.size(); } /// Get a given root - Record *getRoot(size_t index=0) + Record *getRoot(size_t index=0) const { Record *res = roots.at(index); assert(res != NULL); return res; } /// Number of roots - size_t numRoots() { return roots.size(); } + size_t numRoots() const { return roots.size(); } }; @@ -153,96 +153,94 @@ template struct KeyListT { typedef std::vector< KeyT > VecType; - static const int sLinearInterpolation = 1; - static const int sQuadraticInterpolation = 2; - static const int sTBCInterpolation = 3; - static const int sXYZInterpolation = 4; + static const unsigned int sLinearInterpolation = 1; + static const unsigned int sQuadraticInterpolation = 2; + static const unsigned int sTBCInterpolation = 3; + static const unsigned int sXYZInterpolation = 4; - int mInterpolationType; + unsigned int mInterpolationType; VecType mKeys; + //Read in a KeyGroup (see http://niftools.sourceforge.net/doc/nif/NiKeyframeData.html) void read(NIFStream *nif, bool force=false) { - size_t count = nif->getInt(); + assert(nif); + size_t count = nif->getUInt(); if(count == 0 && !force) return; - mInterpolationType = nif->getInt(); - mKeys.resize(count); - if(mInterpolationType == sLinearInterpolation) + //If we aren't forcing things, make sure that read clears any previous keys + if(!force) + mKeys.clear(); + + mInterpolationType = nif->getUInt(); + + KeyT key; + NIFStream &nifReference = *nif; + for(size_t i = 0;i < count;i++) { - for(size_t i = 0;i < count;i++) + if(mInterpolationType == sLinearInterpolation) { - KeyT &key = mKeys[i]; - key.mTime = nif->getFloat(); - key.mValue = (nif->*getValue)(); + readTimeAndValue(nifReference, key); + mKeys.push_back(key); } - } - else if(mInterpolationType == sQuadraticInterpolation) - { - for(size_t i = 0;i < count;i++) + else if(mInterpolationType == sQuadraticInterpolation) { - KeyT &key = mKeys[i]; - key.mTime = nif->getFloat(); - key.mValue = (nif->*getValue)(); - if( typeid(Ogre::Quaternion) != typeid(T) ) - { - key.mForwardValue = (nif->*getValue)(); - key.mBackwardValue = (nif->*getValue)(); - } + readQuadratic(nifReference, key); + mKeys.push_back(key); } - } - else if(mInterpolationType == sTBCInterpolation) - { - for(size_t i = 0;i < count;i++) + else if(mInterpolationType == sTBCInterpolation) { - KeyT &key = mKeys[i]; - key.mTime = nif->getFloat(); - key.mValue = (nif->*getValue)(); - key.mTension = nif->getFloat(); - key.mBias = nif->getFloat(); - key.mContinuity = nif->getFloat(); + readTBC(nifReference, key); + mKeys.push_back(key); } - } - //\FIXME This now reads the correct amount of data in the file, but doesn't actually do anything with it. - else if(mInterpolationType == sXYZInterpolation) - { - if (count != 1) + //XYZ keys aren't actually read here. + //data.hpp sees that the last type read was sXYZInterpolation and: + // Eats a floating point number, then + // Re-runs the read function 3 more times, with force enabled so that the previous values aren't cleared. + // When it does that it's reading in a bunch of sLinearInterpolation keys, not sXYZInterpolation. + else if(mInterpolationType == sXYZInterpolation) { - nif->file->fail("count should always be '1' for XYZ_ROTATION_KEY. Retrieved Value: "+Ogre::StringConverter::toString(count)); - return; + //Don't try to read XYZ keys into the wrong part + if ( count != 1 ) + nif->file->fail("XYZ_ROTATION_KEY count should always be '1' . Retrieved Value: "+Ogre::StringConverter::toString(count)); } - //KeyGroup (see http://niftools.sourceforge.net/doc/nif/NiKeyframeData.html) - //Chomp unknown and possibly unused float - nif->getFloat(); - for(size_t i=0;i<3;++i) + else if (0 == mInterpolationType) { - unsigned int numKeys = nif->getInt(); - if(numKeys != 0) - { - int interpolationTypeAgain = nif->getInt(); - if( interpolationTypeAgain != sLinearInterpolation) - { - nif->file->fail("XYZ_ROTATION_KEY's KeyGroup keyType must be '1' (Linear Interpolation). Retrieved Value: "+Ogre::StringConverter::toString(interpolationTypeAgain)); - return; - } - for(size_t j = 0;j < numKeys;j++) - { - //For now just chomp these - nif->getFloat(); - nif->getFloat(); - } - } - nif->file->warn("XYZ_ROTATION_KEY read, but not used!"); + if (count != 0) + nif->file->fail("Interpolation type 0 doesn't work with keys"); } - } - else if (mInterpolationType == 0) - { - if (count != 0) - nif->file->fail("Interpolation type 0 doesn't work with keys"); + else + nif->file->fail("Unhandled interpolation type: "+Ogre::StringConverter::toString(mInterpolationType)); } - else - nif->file->fail("Unhandled interpolation type: "+Ogre::StringConverter::toString(mInterpolationType)); + } + +private: + static void readTimeAndValue(NIFStream &nif, KeyT &key) + { + key.mTime = nif.getFloat(); + key.mValue = (nif.*getValue)(); + } + + static void readQuadratic(NIFStream &nif, KeyT &key) + { + readTimeAndValue(nif, key); + } + + template + static void readQuadratic(NIFStream &nif, KeyT &key) + { + readTimeAndValue(nif, key); + key.mForwardValue = (nif.*getValue)(); + key.mBackwardValue = (nif.*getValue)(); + } + + static void readTBC(NIFStream &nif, KeyT &key) + { + readTimeAndValue(nif, key); + key.mTension = nif.getFloat(); + key.mBias = nif.getFloat(); + key.mContinuity = nif.getFloat(); } }; typedef KeyListT FloatKeyList; diff --git a/components/translation/translation.cpp b/components/translation/translation.cpp index 423c3971a..976ae926d 100644 --- a/components/translation/translation.cpp +++ b/components/translation/translation.cpp @@ -32,6 +32,9 @@ namespace Translation boost::filesystem::ifstream stream ( dataFileCollections.getCollection (extension).getPath (fileName)); + // Configure the stream to throw exception upon error + stream.exceptions ( boost::filesystem::ifstream::failbit | boost::filesystem::ifstream::badbit ); + if (!stream.is_open()) throw std::runtime_error ("failed to open translation file: " + fileName); @@ -41,6 +44,7 @@ namespace Translation void Storage::loadDataFromStream(ContainerType& container, std::istream& stream) { + // NOTE: does not handle failbit/badbit. stream must be set up beforehand to throw in these cases. std::string line; while (!stream.eof()) { diff --git a/credits.txt b/credits.txt index da5417034..791db0433 100644 --- a/credits.txt +++ b/credits.txt @@ -19,8 +19,8 @@ Alexander Olofsson (Ace) Artem Kotsynyak (greye) Arthur Moore (EmperorArthur) athile +Bret Curtis (psi29a) Britt Mathis (galdor557) -BrotherBrick cc9cii Chris Boyce (slothlife) Chris Robinson (KittyCat) @@ -36,6 +36,7 @@ Eli2 Emanuel Guével (potatoesmaster) Fil Krynicki (filkry) gugus/gus +Hallfaer Tuilinn Jacob Essex (Yacoby) Jannik Heller (scrawl) Jason Hooks (jhooks) @@ -78,7 +79,7 @@ Torben Leif Carrington (TorbenC) Packagers: Alexander Olofsson (Ace) - Windows -BrotherBrick - Ubuntu Linux +Bret Curtis (psi29a) - Ubuntu Linux Edmondo Tommasina (edmondo) - Gentoo Linux Julian Ospald (hasufell) - Gentoo Linux Karl-Felix Glatzer (k1ll) - Linux Binaries diff --git a/Docs/Doxyfile b/docs/Doxyfile similarity index 99% rename from Docs/Doxyfile rename to docs/Doxyfile index 43c3312ad..156e23abd 100644 --- a/Docs/Doxyfile +++ b/docs/Doxyfile @@ -576,7 +576,7 @@ WARN_LOGFILE = INPUT = apps \ components \ libs \ - Docs + docs # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is diff --git a/Docs/DoxyfilePages b/docs/DoxyfilePages similarity index 99% rename from Docs/DoxyfilePages rename to docs/DoxyfilePages index 5ce82a7c2..dca9d7a12 100644 --- a/Docs/DoxyfilePages +++ b/docs/DoxyfilePages @@ -576,7 +576,7 @@ WARN_LOGFILE = INPUT = apps \ components \ libs \ - Docs + docs # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is diff --git a/DejaVu Font License.txt b/docs/license/DejaVu Font License.txt similarity index 100% rename from DejaVu Font License.txt rename to docs/license/DejaVu Font License.txt diff --git a/GPL3.txt b/docs/license/GPL3.txt similarity index 100% rename from GPL3.txt rename to docs/license/GPL3.txt diff --git a/Docs/mainpage.hpp.cmake b/docs/mainpage.hpp.cmake similarity index 100% rename from Docs/mainpage.hpp.cmake rename to docs/mainpage.hpp.cmake diff --git a/extern/oics/ICSChannel.cpp b/extern/oics/ICSChannel.cpp index 703f2207c..178fe5aa3 100644 --- a/extern/oics/ICSChannel.cpp +++ b/extern/oics/ICSChannel.cpp @@ -38,6 +38,7 @@ namespace ICS , mValue(initialValue) , mSymmetricAt(symmetricAt) , mBezierStep(bezierStep) + , mEnabled(true) { mBezierMidPoint.x = bezierMidPointX; mBezierMidPoint.y = bezierMidPointY; @@ -45,6 +46,11 @@ namespace ICS setBezierFunction(bezierMidPointY, bezierMidPointX, symmetricAt, bezierStep); } + void Channel::setEnabled(bool enabled) + { + mEnabled = enabled; + } + float Channel::getValue() { if(mValue == 0 || mValue == 1) @@ -78,7 +84,7 @@ namespace ICS mValue = value; - if(previousValue != value) + if(previousValue != value && mEnabled) { notifyListeners(previousValue); } @@ -124,7 +130,7 @@ namespace ICS void Channel::update() { - if(this->getControlsCount() == 1) + if(this->getControlsCount() == 1) { ControlChannelBinderItem ccBinderItem = mAttachedControls.back(); float diff = ccBinderItem.control->getValue() - ccBinderItem.control->getInitialValue(); @@ -255,4 +261,4 @@ namespace ICS t += 1.0f / ((endX-startX)/step); } } -} \ No newline at end of file +} diff --git a/extern/oics/ICSChannel.h b/extern/oics/ICSChannel.h index 5ec6cd575..3da53369c 100644 --- a/extern/oics/ICSChannel.h +++ b/extern/oics/ICSChannel.h @@ -89,6 +89,8 @@ namespace ICS IntervalList& getIntervals(){ return mIntervals; }; + void setEnabled(bool enabled); + protected: int mNumber; @@ -112,7 +114,9 @@ namespace ICS std::vector mAttachedControls; std::list mListeners; - void notifyListeners(float previousValue); + void notifyListeners(float previousValue); + + bool mEnabled; }; diff --git a/extern/oics/ICSInputControlSystem.cpp b/extern/oics/ICSInputControlSystem.cpp index cdf8fbfe2..a053bbab4 100644 --- a/extern/oics/ICSInputControlSystem.cpp +++ b/extern/oics/ICSInputControlSystem.cpp @@ -796,6 +796,11 @@ namespace ICS mMouseAxisBindingInitialValues[0] = ICS_MOUSE_AXIS_BINDING_NULL_VALUE; } + bool InputControlSystem::detectingBindingState() + { + return mDetectingBindingControl != NULL; + } + void InputControlSystem::cancelDetectingBindingState() { mDetectingBindingControl = NULL; diff --git a/extern/oics/ICSInputControlSystem.h b/extern/oics/ICSInputControlSystem.h index a83ae539e..4aaecdea9 100644 --- a/extern/oics/ICSInputControlSystem.h +++ b/extern/oics/ICSInputControlSystem.h @@ -146,6 +146,7 @@ namespace ICS void enableDetectingBindingState(Control* control, Control::ControlChangingDirection direction); void cancelDetectingBindingState(); + bool detectingBindingState(); bool save(std::string fileName = ""); diff --git a/files/mygui/openmw_alchemy_window.layout b/files/mygui/openmw_alchemy_window.layout index 4b15ac1bd..e098800af 100644 --- a/files/mygui/openmw_alchemy_window.layout +++ b/files/mygui/openmw_alchemy_window.layout @@ -23,21 +23,13 @@ - - - + - - - + - - - + - - - + @@ -51,21 +43,13 @@ - - - + - - - + - - - + - - - + @@ -106,4 +90,4 @@ - \ No newline at end of file + diff --git a/files/mygui/openmw_box.skin.xml b/files/mygui/openmw_box.skin.xml index 620f49e2b..af3a05d31 100644 --- a/files/mygui/openmw_box.skin.xml +++ b/files/mygui/openmw_box.skin.xml @@ -73,4 +73,9 @@ as around the sections of the stats window, or around popup info windows --> + + + + + diff --git a/files/mygui/openmw_dialogue_window.layout b/files/mygui/openmw_dialogue_window.layout index 78daa0705..5a7cd772d 100644 --- a/files/mygui/openmw_dialogue_window.layout +++ b/files/mygui/openmw_dialogue_window.layout @@ -4,28 +4,28 @@ - + - - + + - + - - + - + diff --git a/files/mygui/openmw_enchanting_dialog.layout b/files/mygui/openmw_enchanting_dialog.layout index f64d21dea..2a3cb7c1d 100644 --- a/files/mygui/openmw_enchanting_dialog.layout +++ b/files/mygui/openmw_enchanting_dialog.layout @@ -26,8 +26,7 @@ - - + @@ -35,8 +34,7 @@ - - + diff --git a/files/mygui/openmw_hud.layout b/files/mygui/openmw_hud.layout index 90fa1c8a5..e1f8af7bf 100644 --- a/files/mygui/openmw_hud.layout +++ b/files/mygui/openmw_hud.layout @@ -59,7 +59,7 @@ - + @@ -71,7 +71,7 @@ - + diff --git a/files/mygui/openmw_quickkeys_menu.layout b/files/mygui/openmw_quickkeys_menu.layout index dcb10404d..d7e976d45 100644 --- a/files/mygui/openmw_quickkeys_menu.layout +++ b/files/mygui/openmw_quickkeys_menu.layout @@ -17,16 +17,16 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/files/mygui/openmw_recharge_dialog.layout b/files/mygui/openmw_recharge_dialog.layout index 49e735764..1b2ad947b 100644 --- a/files/mygui/openmw_recharge_dialog.layout +++ b/files/mygui/openmw_recharge_dialog.layout @@ -4,7 +4,7 @@ - + diff --git a/files/mygui/openmw_repair.layout b/files/mygui/openmw_repair.layout index 2881a5853..09a372440 100644 --- a/files/mygui/openmw_repair.layout +++ b/files/mygui/openmw_repair.layout @@ -4,7 +4,7 @@ - + diff --git a/files/mygui/openmw_resources.xml b/files/mygui/openmw_resources.xml index 3901ab825..a37d3335c 100644 --- a/files/mygui/openmw_resources.xml +++ b/files/mygui/openmw_resources.xml @@ -319,4 +319,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/mygui/openmw_savegame_dialog.layout b/files/mygui/openmw_savegame_dialog.layout index ceb1a8428..701533636 100644 --- a/files/mygui/openmw_savegame_dialog.layout +++ b/files/mygui/openmw_savegame_dialog.layout @@ -2,8 +2,8 @@ - - + + @@ -49,10 +49,14 @@ - + + + + + diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 9eed2c7d9..2ed3d6cb9 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -168,6 +168,8 @@ camera y multiplier = 1.0 ui y multiplier = 1.0 +always run = false + [Game] # Always use the most powerful attack when striking with a weapon (chop, slash or thrust) best attack = false