diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..25cc3fffc --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +root = true + +[*.cpp] +indent_style = space +indent_size = 4 +insert_final_newline = true + +[*.hpp] +indent_style = space +indent_size = 4 +insert_final_newline = true \ No newline at end of file diff --git a/.gitignore b/.gitignore index 5d98a8317..e37fc7721 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ Doxygen .settings .directory .idea +files/windows/*.aps cmake-build-* ## qt-creator CMakeLists.txt.user* diff --git a/AUTHORS.md b/AUTHORS.md index 95c741eab..bb773c4ef 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -60,10 +60,11 @@ Programmers Gašper Sedej gugus/gus Hallfaer Tuilinn + Haoda Wang (h313) hristoast Internecine - Jacob Essex (Yacoby) - Jannik Heller (scrawl) + Jacob Essex (Yacoby) + Jake Westrip (16bitint) Jason Hooks (jhooks) jeaye Jeffrey Haines (Jyby) @@ -132,12 +133,14 @@ Programmers Roman Proskuryakov (kpp) Sandy Carter (bwrsandman) Scott Howard + scrawl Sebastian Wick (swick) Sergey Shambir ShadowRadiance Siimacore sir_herrbatka smbas + spycrab Stefan Galowicz (bogglez) Stanislav Bobrov (Jiub) stil-t diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 1c1d57016..10f3ae81b 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -172,7 +172,10 @@ Launcher::FirstRunDialogResult Launcher::MainDialog::showFirstRunDialog() } } - return setup() ? FirstRunDialogResultContinue : FirstRunDialogResultFailure; + if (!setup() || !setupGameData()) { + return FirstRunDialogResultFailure; + } + return FirstRunDialogResultContinue; } void Launcher::MainDialog::setVersionLabel() @@ -344,6 +347,11 @@ bool Launcher::MainDialog::setupGameSettings() file.close(); } + return true; +} + +bool Launcher::MainDialog::setupGameData() +{ QStringList dataDirs; // Check if the paths actually contain data files diff --git a/apps/launcher/maindialog.hpp b/apps/launcher/maindialog.hpp index 96b5c0b97..8d0d61b8f 100644 --- a/apps/launcher/maindialog.hpp +++ b/apps/launcher/maindialog.hpp @@ -72,6 +72,7 @@ namespace Launcher bool setupLauncherSettings(); bool setupGameSettings(); bool setupGraphicsSettings(); + bool setupGameData(); void setVersionLabel(); diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 54e323956..86c87962d 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -5,9 +5,6 @@ #include #include -#include -#include - #include #include @@ -33,11 +30,7 @@ CS::Editor::Editor () NifOsg::Loader::setShowMarkers(true); - mVFS.reset(new VFS::Manager(mFsStrict)); - - VFS::registerArchives(mVFS.get(), Files::Collections(config.first, !mFsStrict), config.second, true); - - mDocumentManager.setVFS(mVFS.get()); + mDocumentManager.setFileData(mFsStrict, config.first, config.second); mNewGame.setLocalData (mLocal); mFileDialog.setLocalData (mLocal); diff --git a/apps/opencs/editor.hpp b/apps/opencs/editor.hpp index ff670ba1b..b60f5c6a8 100644 --- a/apps/opencs/editor.hpp +++ b/apps/opencs/editor.hpp @@ -1,8 +1,6 @@ #ifndef CS_EDITOR_H #define CS_EDITOR_H -#include - #include #include @@ -30,11 +28,6 @@ #include "view/tools/merge.hpp" -namespace VFS -{ - class Manager; -} - namespace CSMDoc { class Document; @@ -46,9 +39,6 @@ namespace CS { Q_OBJECT - // FIXME: should be moved to document, so we can have different resources for each opened project - std::unique_ptr mVFS; - Files::ConfigurationManager mCfgMgr; CSMPrefs::State mSettingsState; CSMDoc::DocumentManager mDocumentManager; diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index ef984c0ca..7257b2fe3 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -269,13 +269,14 @@ void CSMDoc::Document::createBase() } } -CSMDoc::Document::Document (const VFS::Manager* vfs, const Files::ConfigurationManager& configuration, - const std::vector< boost::filesystem::path >& files, bool new_, +CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, + const std::vector< boost::filesystem::path >& files,bool new_, const boost::filesystem::path& savePath, const boost::filesystem::path& resDir, const Fallback::Map* fallback, - ToUTF8::FromType encoding, const CSMWorld::ResourcesManager& resourcesManager, - const std::vector& blacklistedScripts) -: mVFS(vfs), mSavePath (savePath), mContentFiles (files), mNew (new_), mData (encoding, resourcesManager, fallback, resDir), + ToUTF8::FromType encoding, + const std::vector& blacklistedScripts, + bool fsStrict, const Files::PathContainer& dataPaths, const std::vector& archives) +: mSavePath (savePath), mContentFiles (files), mNew (new_), mData (encoding, fsStrict, dataPaths, archives, fallback, resDir), mTools (*this, encoding), mProjectPath ((configuration.getUserDataPath() / "projects") / (savePath.filename().string() + ".project")), @@ -337,11 +338,6 @@ CSMDoc::Document::~Document() { } -const VFS::Manager *CSMDoc::Document::getVFS() const -{ - return mVFS; -} - QUndoStack& CSMDoc::Document::getUndoStack() { return mUndoStack; diff --git a/apps/opencs/model/doc/document.hpp b/apps/opencs/model/doc/document.hpp index 030a0174e..d31fd5aca 100644 --- a/apps/opencs/model/doc/document.hpp +++ b/apps/opencs/model/doc/document.hpp @@ -9,6 +9,7 @@ #include #include +#include #include #include "../world/data.hpp" @@ -59,7 +60,6 @@ namespace CSMDoc private: - const VFS::Manager* mVFS; boost::filesystem::path mSavePath; std::vector mContentFiles; bool mNew; @@ -102,17 +102,15 @@ namespace CSMDoc public: - Document (const VFS::Manager* vfs, const Files::ConfigurationManager& configuration, + Document (const Files::ConfigurationManager& configuration, const std::vector< boost::filesystem::path >& files, bool new_, const boost::filesystem::path& savePath, const boost::filesystem::path& resDir, - const Fallback::Map* fallback, - ToUTF8::FromType encoding, const CSMWorld::ResourcesManager& resourcesManager, - const std::vector& blacklistedScripts); + const Fallback::Map* fallback, ToUTF8::FromType encoding, + const std::vector& blacklistedScripts, + bool fsStrict, const Files::PathContainer& dataPaths, const std::vector& archives); ~Document(); - const VFS::Manager* getVFS() const; - QUndoStack& getUndoStack(); int getState() const; diff --git a/apps/opencs/model/doc/documentmanager.cpp b/apps/opencs/model/doc/documentmanager.cpp index 098d7bad5..531cfd267 100644 --- a/apps/opencs/model/doc/documentmanager.cpp +++ b/apps/opencs/model/doc/documentmanager.cpp @@ -9,7 +9,7 @@ #include "document.hpp" CSMDoc::DocumentManager::DocumentManager (const Files::ConfigurationManager& configuration) -: mConfiguration (configuration), mEncoding (ToUTF8::WINDOWS_1252), mVFS(NULL) +: mConfiguration (configuration), mEncoding (ToUTF8::WINDOWS_1252) { boost::filesystem::path projectPath = configuration.getUserDataPath() / "projects"; @@ -62,7 +62,7 @@ CSMDoc::Document *CSMDoc::DocumentManager::makeDocument ( const std::vector< boost::filesystem::path >& files, const boost::filesystem::path& savePath, bool new_) { - return new Document (mVFS, mConfiguration, files, new_, savePath, mResDir, &mFallbackMap, mEncoding, mResourcesManager, mBlacklistedScripts); + return new Document (mConfiguration, files, new_, savePath, mResDir, &mFallbackMap, mEncoding, mBlacklistedScripts, mFsStrict, mDataPaths, mArchives); } void CSMDoc::DocumentManager::insertDocument (CSMDoc::Document *document) @@ -127,8 +127,9 @@ void CSMDoc::DocumentManager::documentNotLoaded (Document *document, const std:: removeDocument (document); } -void CSMDoc::DocumentManager::setVFS(const VFS::Manager *vfs) +void CSMDoc::DocumentManager::setFileData(bool strict, const Files::PathContainer& dataPaths, const std::vector& archives) { - mResourcesManager.setVFS(vfs); - mVFS = vfs; + mFsStrict = strict; + mDataPaths = dataPaths; + mArchives = archives; } diff --git a/apps/opencs/model/doc/documentmanager.hpp b/apps/opencs/model/doc/documentmanager.hpp index ed8e327d7..ae6d9481a 100644 --- a/apps/opencs/model/doc/documentmanager.hpp +++ b/apps/opencs/model/doc/documentmanager.hpp @@ -11,8 +11,7 @@ #include #include - -#include "../world/resourcesmanager.hpp" +#include #include "loader.hpp" @@ -39,9 +38,14 @@ namespace CSMDoc QThread mLoaderThread; Loader mLoader; ToUTF8::FromType mEncoding; - CSMWorld::ResourcesManager mResourcesManager; std::vector mBlacklistedScripts; - const VFS::Manager* mVFS; + + boost::filesystem::path mResDir; + Fallback::Map mFallbackMap; + + bool mFsStrict; + Files::PathContainer mDataPaths; + std::vector mArchives; DocumentManager (const DocumentManager&); DocumentManager& operator= (const DocumentManager&); @@ -74,15 +78,11 @@ namespace CSMDoc void setBlacklistedScripts (const std::vector& scriptIds); - void setVFS(const VFS::Manager* vfs); + /// Sets the file data that gets passed to newly created documents. + void setFileData(bool strict, const Files::PathContainer& dataPaths, const std::vector& archives); bool isEmpty(); - private: - - boost::filesystem::path mResDir; - Fallback::Map mFallbackMap; - private slots: void documentLoaded (Document *document); diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index c87e283a8..5c0b2e282 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -259,6 +259,7 @@ void CSMPrefs::State::declare() declareShortcut ("document-character-topicinfos", "Open Topic Info List", QKeySequence()); declareShortcut ("document-character-journalinfos", "Open Journal Info List", QKeySequence()); declareShortcut ("document-character-bodyparts", "Open Body Part List", QKeySequence()); + declareShortcut ("document-assets-reload", "Reload Assets", QKeySequence(Qt::Key_F5)); declareShortcut ("document-assets-sounds", "Open Sound Asset List", QKeySequence()); declareShortcut ("document-assets-soundgens", "Open Sound Generator List", QKeySequence()); declareShortcut ("document-assets-meshes", "Open Mesh Asset List", QKeySequence()); diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index ee9e4329c..007190e4a 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -11,6 +11,8 @@ #include #include +#include +#include #include "idtable.hpp" #include "idtree.hpp" @@ -61,11 +63,18 @@ int CSMWorld::Data::count (RecordBase::State state, const CollectionBase& collec return number; } -CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourcesManager, const Fallback::Map* fallback, const boost::filesystem::path& resDir) +CSMWorld::Data::Data (ToUTF8::FromType encoding, bool fsStrict, const Files::PathContainer& dataPaths, + const std::vector& archives, const Fallback::Map* fallback, const boost::filesystem::path& resDir) : mEncoder (encoding), mPathgrids (mCells), mRefs (mCells), - mResourcesManager (resourcesManager), mFallbackMap(fallback), - mReader (0), mDialogue (0), mReaderIndex(1), mResourceSystem(new Resource::ResourceSystem(resourcesManager.getVFS())) + mFallbackMap(fallback), mReader (0), mDialogue (0), mReaderIndex(1), + mFsStrict(fsStrict), mDataPaths(dataPaths), mArchives(archives) { + mVFS.reset(new VFS::Manager(mFsStrict)); + VFS::registerArchives(mVFS.get(), Files::Collections(mDataPaths, !mFsStrict), mArchives, true); + + mResourcesManager.setVFS(mVFS.get()); + mResourceSystem.reset(new Resource::ResourceSystem(mVFS.get())); + mResourceSystem->getSceneManager()->setShaderPath((resDir / "shaders").string()); int index = 0; @@ -1215,6 +1224,43 @@ std::vector CSMWorld::Data::getIds (bool listDeleted) const return ids; } +void CSMWorld::Data::assetsChanged() +{ + mVFS.get()->reset(); + VFS::registerArchives(mVFS.get(), Files::Collections(mDataPaths, !mFsStrict), mArchives, true); + + const UniversalId assetTableIds[] = { + UniversalId::Type_Meshes, + UniversalId::Type_Icons, + UniversalId::Type_Musics, + UniversalId::Type_SoundsRes, + UniversalId::Type_Textures, + UniversalId::Type_Videos + }; + + size_t numAssetTables = sizeof(assetTableIds) / sizeof(UniversalId); + + for (size_t i = 0; i < numAssetTables; ++i) + { + ResourceTable* table = static_cast(getTableModel(assetTableIds[i])); + table->beginReset(); + } + + // Trigger recreation + mResourcesManager.recreateResources(); + + for (size_t i = 0; i < numAssetTables; ++i) + { + ResourceTable* table = static_cast(getTableModel(assetTableIds[i])); + table->endReset(); + } + + // Get rid of potentially old cached assets + mResourceSystem->clearCache(); + + emit assetTablesChanged(); +} + void CSMWorld::Data::dataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { if (topLeft.column()<=0) @@ -1228,7 +1274,7 @@ void CSMWorld::Data::rowsChanged (const QModelIndex& parent, int start, int end) const VFS::Manager* CSMWorld::Data::getVFS() const { - return mResourcesManager.getVFS(); + return mVFS.get(); } const Fallback::Map* CSMWorld::Data::getFallbackMap() const diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index f96003e44..8a3667ea1 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -31,6 +31,7 @@ #include +#include #include #include "../doc/stage.hpp" @@ -46,6 +47,7 @@ #include "infocollection.hpp" #include "nestedinfocollection.hpp" #include "pathgrid.hpp" +#include "resourcesmanager.hpp" #include "metadata.hpp" #ifndef Q_MOC_RUN #include "subcellcollection.hpp" @@ -108,7 +110,6 @@ namespace CSMWorld RefCollection mRefs; IdCollection mFilters; Collection mMetaData; - const ResourcesManager& mResourcesManager; const Fallback::Map* mFallbackMap; std::vector mModels; std::map mModelIndex; @@ -119,6 +120,11 @@ namespace CSMWorld std::map > mRefLoadCache; int mReaderIndex; + bool mFsStrict; + Files::PathContainer mDataPaths; + std::vector mArchives; + std::unique_ptr mVFS; + ResourcesManager mResourcesManager; std::shared_ptr mResourceSystem; std::vector > mReaders; @@ -140,7 +146,9 @@ namespace CSMWorld public: - Data (ToUTF8::FromType encoding, const ResourcesManager& resourcesManager, const Fallback::Map* fallback, const boost::filesystem::path& resDir); + Data (ToUTF8::FromType encoding, bool fsStrict, const Files::PathContainer& dataPaths, + const std::vector& archives, const Fallback::Map* fallback, + const boost::filesystem::path& resDir); virtual ~Data(); @@ -304,8 +312,12 @@ namespace CSMWorld void idListChanged(); + void assetTablesChanged(); + private slots: + void assetsChanged(); + void dataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); void rowsChanged (const QModelIndex& parent, int start, int end); diff --git a/apps/opencs/model/world/resources.cpp b/apps/opencs/model/world/resources.cpp index 5fe9194d7..5bf0267bb 100644 --- a/apps/opencs/model/world/resources.cpp +++ b/apps/opencs/model/world/resources.cpp @@ -12,6 +12,14 @@ CSMWorld::Resources::Resources (const VFS::Manager* vfs, const std::string& base const char * const *extensions) : mBaseDirectory (baseDirectory), mType (type) { + recreate(vfs, extensions); +} + +void CSMWorld::Resources::recreate(const VFS::Manager* vfs, const char * const *extensions) +{ + mFiles.clear(); + mIndex.clear(); + int baseSize = mBaseDirectory.size(); const std::map& index = vfs->getIndex(); diff --git a/apps/opencs/model/world/resources.hpp b/apps/opencs/model/world/resources.hpp index d6998da9f..5e9872ea8 100644 --- a/apps/opencs/model/world/resources.hpp +++ b/apps/opencs/model/world/resources.hpp @@ -27,6 +27,8 @@ namespace CSMWorld Resources (const VFS::Manager* vfs, const std::string& baseDirectory, UniversalId::Type type, const char * const *extensions = 0); + void recreate(const VFS::Manager* vfs, const char * const *extensions = 0); + int getSize() const; std::string getId (int index) const; diff --git a/apps/opencs/model/world/resourcesmanager.cpp b/apps/opencs/model/world/resourcesmanager.cpp index 016799be3..62dfe53a9 100644 --- a/apps/opencs/model/world/resourcesmanager.cpp +++ b/apps/opencs/model/world/resourcesmanager.cpp @@ -14,21 +14,24 @@ void CSMWorld::ResourcesManager::addResources (const Resources& resources) resources)); } +const char * const * CSMWorld::ResourcesManager::getMeshExtensions() +{ + // maybe we could go over the osgDB::Registry to list all supported node formats + static const char * const sMeshTypes[] = { "nif", "osg", "osgt", "osgb", "osgx", "osg2", 0 }; + return sMeshTypes; +} + void CSMWorld::ResourcesManager::setVFS(const VFS::Manager *vfs) { mVFS = vfs; mResources.clear(); - // maybe we could go over the osgDB::Registry to list all supported node formats - - static const char * const sMeshTypes[] = { "nif", "osg", "osgt", "osgb", "osgx", "osg2", 0 }; - - addResources (Resources (vfs, "meshes", UniversalId::Type_Mesh, sMeshTypes)); + addResources (Resources (vfs, "meshes", UniversalId::Type_Mesh, getMeshExtensions())); addResources (Resources (vfs, "icons", UniversalId::Type_Icon)); addResources (Resources (vfs, "music", UniversalId::Type_Music)); addResources (Resources (vfs, "sound", UniversalId::Type_SoundRes)); addResources (Resources (vfs, "textures", UniversalId::Type_Texture)); - addResources (Resources (vfs, "videos", UniversalId::Type_Video)); + addResources (Resources (vfs, "video", UniversalId::Type_Video)); } const VFS::Manager* CSMWorld::ResourcesManager::getVFS() const @@ -36,6 +39,18 @@ const VFS::Manager* CSMWorld::ResourcesManager::getVFS() const return mVFS; } +void CSMWorld::ResourcesManager::recreateResources() +{ + std::map::iterator it = mResources.begin(); + for ( ; it != mResources.end(); ++it) + { + if (it->first == UniversalId::Type_Mesh) + it->second.recreate(mVFS, getMeshExtensions()); + else + it->second.recreate(mVFS); + } +} + const CSMWorld::Resources& CSMWorld::ResourcesManager::get (UniversalId::Type type) const { std::map::const_iterator iter = mResources.find (type); diff --git a/apps/opencs/model/world/resourcesmanager.hpp b/apps/opencs/model/world/resourcesmanager.hpp index 1ce06f2d3..0e8385300 100644 --- a/apps/opencs/model/world/resourcesmanager.hpp +++ b/apps/opencs/model/world/resourcesmanager.hpp @@ -22,6 +22,8 @@ namespace CSMWorld void addResources (const Resources& resources); + const char * const * getMeshExtensions(); + public: ResourcesManager(); @@ -30,6 +32,8 @@ namespace CSMWorld void setVFS(const VFS::Manager* vfs); + void recreateResources(); + const Resources& get (UniversalId::Type type) const; }; } diff --git a/apps/opencs/model/world/resourcetable.cpp b/apps/opencs/model/world/resourcetable.cpp index 5227ec3e6..f55f87873 100644 --- a/apps/opencs/model/world/resourcetable.cpp +++ b/apps/opencs/model/world/resourcetable.cpp @@ -154,3 +154,13 @@ int CSMWorld::ResourceTable::getColumnId (int column) const return -1; } + +void CSMWorld::ResourceTable::beginReset() +{ + beginResetModel(); +} + +void CSMWorld::ResourceTable::endReset() +{ + endResetModel(); +} diff --git a/apps/opencs/model/world/resourcetable.hpp b/apps/opencs/model/world/resourcetable.hpp index 88dcc24b0..7d538df53 100644 --- a/apps/opencs/model/world/resourcetable.hpp +++ b/apps/opencs/model/world/resourcetable.hpp @@ -52,7 +52,12 @@ namespace CSMWorld /// Is \a id flagged as deleted? virtual bool isDeleted (const std::string& id) const; - virtual int getColumnId (int column) const; + virtual int getColumnId (int column) const; + + /// Signal Qt that the data is about to change. + void beginReset(); + /// Signal Qt that the data has been changed. + void endReset(); }; } diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index ff49a90fd..dfbeea031 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -284,6 +284,13 @@ void CSVDoc::View::setupAssetsMenu() { QMenu *assets = menuBar()->addMenu (tr ("Assets")); + QAction *reload = new QAction (tr ("Reload"), this); + connect (reload, SIGNAL (triggered()), &mDocument->getData(), SLOT (assetsChanged())); + setupShortcut("document-assets-reload", reload); + assets->addAction (reload); + + assets->addSeparator(); + QAction *sounds = new QAction (tr ("Sounds"), this); connect (sounds, SIGNAL (triggered()), this, SLOT (addSoundsSubView())); setupShortcut("document-assets-sounds", sounds); diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index 48156359f..765c5b316 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -275,12 +275,32 @@ bool CSVRender::Cell::referenceAdded (const QModelIndex& parent, int start, int void CSVRender::Cell::pathgridModified() { - mPathgrid->recreateGeometry(); + if (mPathgrid) + mPathgrid->recreateGeometry(); } void CSVRender::Cell::pathgridRemoved() { - mPathgrid->removeGeometry(); + if (mPathgrid) + mPathgrid->removeGeometry(); +} + +void CSVRender::Cell::reloadAssets() +{ + for (std::map::const_iterator iter (mObjects.begin()); + iter != mObjects.end(); ++iter) + { + iter->second->reloadAssets(); + } + + if (mTerrain) + { + mTerrain->unloadCell(mCoordinates.getX(), mCoordinates.getY()); + mTerrain->loadCell(mCoordinates.getX(), mCoordinates.getY()); + } + + if (mCellWater) + mCellWater->reloadAssets(); } void CSVRender::Cell::setSelection (int elementMask, Selection mode) @@ -302,7 +322,7 @@ void CSVRender::Cell::setSelection (int elementMask, Selection mode) iter->second->setSelected (selected); } } - if (elementMask & Mask_Pathgrid) + if (mPathgrid && elementMask & Mask_Pathgrid) { // Only one pathgrid may be selected, so some operations will only have an effect // if the pathgrid is already focused @@ -402,7 +422,7 @@ std::vector > CSVRender::Cell::getSelection (un iter!=mObjects.end(); ++iter) if (iter->second->getSelected()) result.push_back (iter->second->getTag()); - if (elementMask & Mask_Pathgrid) + if (mPathgrid && elementMask & Mask_Pathgrid) if (mPathgrid->isSelected()) result.push_back(mPathgrid->getTag()); @@ -439,6 +459,6 @@ void CSVRender::Cell::reset (unsigned int elementMask) for (std::map::const_iterator iter (mObjects.begin()); iter!=mObjects.end(); ++iter) iter->second->reset(); - if (elementMask & Mask_Pathgrid) + if (mPathgrid && elementMask & Mask_Pathgrid) mPathgrid->resetIndicators(); } diff --git a/apps/opencs/view/render/cell.hpp b/apps/opencs/view/render/cell.hpp index ca82dd580..f53f61973 100644 --- a/apps/opencs/view/render/cell.hpp +++ b/apps/opencs/view/render/cell.hpp @@ -118,6 +118,8 @@ namespace CSVRender void pathgridRemoved(); + void reloadAssets(); + void setSelection (int elementMask, Selection mode); // Select everything that references the same ID as at least one of the elements diff --git a/apps/opencs/view/render/cellwater.cpp b/apps/opencs/view/render/cellwater.cpp index 15ea15cc4..d7e8669ef 100644 --- a/apps/opencs/view/render/cellwater.cpp +++ b/apps/opencs/view/render/cellwater.cpp @@ -92,6 +92,11 @@ namespace CSVRender } } + void CellWater::reloadAssets() + { + recreate(); + } + void CellWater::cellDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { const CSMWorld::Collection& cells = mData.getCells(); diff --git a/apps/opencs/view/render/cellwater.hpp b/apps/opencs/view/render/cellwater.hpp index d2ed9b458..47e586707 100644 --- a/apps/opencs/view/render/cellwater.hpp +++ b/apps/opencs/view/render/cellwater.hpp @@ -42,6 +42,8 @@ namespace CSVRender void updateCellData(const CSMWorld::Record& cellRecord); + void reloadAssets(); + private slots: void cellDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index eb90e9db3..522057097 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -532,6 +532,12 @@ bool CSVRender::Object::referenceDataChanged (const QModelIndex& topLeft, return false; } +void CSVRender::Object::reloadAssets() +{ + update(); + updateMarker(); +} + std::string CSVRender::Object::getReferenceId() const { return mReferenceId; diff --git a/apps/opencs/view/render/object.hpp b/apps/opencs/view/render/object.hpp index e28e2562c..e14697e62 100644 --- a/apps/opencs/view/render/object.hpp +++ b/apps/opencs/view/render/object.hpp @@ -151,6 +151,9 @@ namespace CSVRender /// this object? bool referenceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); + /// Reloads the underlying asset + void reloadAssets(); + /// Returns an empty string if this is a refereceable-type object. std::string getReferenceId() const; diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index b5497558a..b1077139c 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -472,6 +472,9 @@ CSVRender::PagedWorldspaceWidget::PagedWorldspaceWidget (QWidget* parent, CSMDoc connect (cells, SIGNAL (rowsInserted (const QModelIndex&, int, int)), this, SLOT (cellAdded (const QModelIndex&, int, int))); + connect (&document.getData(), SIGNAL (assetTablesChanged ()), + this, SLOT (assetTablesChanged ())); + // Shortcuts CSMPrefs::Shortcut* loadCameraCellShortcut = new CSMPrefs::Shortcut("scene-load-cam-cell", this); connect(loadCameraCellShortcut, SIGNAL(activated()), this, SLOT(loadCameraCell())); @@ -520,7 +523,7 @@ void CSVRender::PagedWorldspaceWidget::useViewHint (const std::string& hint) // Loop through all the coordinates to add them to selection while (stream >> ignore1 >> ignore2 >> x >> y) selection.add (CSMWorld::CellCoordinates (x, y)); - + // Mark that camera needs setup mCamPositionSet=false; } @@ -763,6 +766,15 @@ void CSVRender::PagedWorldspaceWidget::cellAdded (const QModelIndex& index, int flagAsModified(); } +void CSVRender::PagedWorldspaceWidget::assetTablesChanged() +{ + std::map::iterator iter = mCells.begin(); + for ( ; iter != mCells.end(); ++iter) + { + iter->second->reloadAssets(); + } +} + void CSVRender::PagedWorldspaceWidget::loadCameraCell() { addCellToSceneFromCamera(0, 0); diff --git a/apps/opencs/view/render/pagedworldspacewidget.hpp b/apps/opencs/view/render/pagedworldspacewidget.hpp index 0663d3424..8c41df51e 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.hpp +++ b/apps/opencs/view/render/pagedworldspacewidget.hpp @@ -84,7 +84,7 @@ namespace CSVRender /// hint system. virtual ~PagedWorldspaceWidget(); - + /// Decodes the the hint string to set of cell that are rendered. void useViewHint (const std::string& hint); @@ -155,6 +155,8 @@ namespace CSVRender virtual void cellAdded (const QModelIndex& index, int start, int end); + void assetTablesChanged (); + void loadCameraCell(); void loadEastCell(); diff --git a/apps/opencs/view/render/pathgridmode.cpp b/apps/opencs/view/render/pathgridmode.cpp index 228b2b5e7..a9cce0200 100644 --- a/apps/opencs/view/render/pathgridmode.cpp +++ b/apps/opencs/view/render/pathgridmode.cpp @@ -72,12 +72,15 @@ namespace CSVRender } else if (Cell* cell = getWorldspaceWidget().getCell (hitResult.worldPos)) { - // Add node - QUndoStack& undoStack = getWorldspaceWidget().getDocument().getUndoStack(); - QString description = "Add node"; + if (cell->getPathgrid()) + { + // Add node + QUndoStack& undoStack = getWorldspaceWidget().getDocument().getUndoStack(); + QString description = "Add node"; - CSMWorld::CommandMacro macro(undoStack, description); - cell->getPathgrid()->applyPoint(macro, hitResult.worldPos); + CSMWorld::CommandMacro macro(undoStack, description); + cell->getPathgrid()->applyPoint(macro, hitResult.worldPos); + } } } @@ -205,7 +208,7 @@ namespace CSVRender WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); Cell* cell = getWorldspaceWidget().getCell(hit.worldPos); - if (cell) + if (cell && cell->getPathgrid()) { PathgridTag* tag = 0; if (hit.tag && (tag = dynamic_cast(hit.tag.get())) && tag->getPathgrid()->getId() == mEdgeId) diff --git a/apps/opencs/view/render/previewwidget.cpp b/apps/opencs/view/render/previewwidget.cpp index 2f3510317..972fb556d 100644 --- a/apps/opencs/view/render/previewwidget.cpp +++ b/apps/opencs/view/render/previewwidget.cpp @@ -17,6 +17,9 @@ CSVRender::PreviewWidget::PreviewWidget (CSMWorld::Data& data, connect (referenceables, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), this, SLOT (referenceableAboutToBeRemoved (const QModelIndex&, int, int))); + connect (&mData, SIGNAL (assetTablesChanged ()), + this, SLOT (assetTablesChanged ())); + if (!referenceable) { QAbstractItemModel *references = @@ -119,3 +122,8 @@ void CSVRender::PreviewWidget::referenceAboutToBeRemoved (const QModelIndex& par if (index.row()>=start && index.row()<=end) emit closeRequest(); } + +void CSVRender::PreviewWidget::assetTablesChanged () +{ + mObject.reloadAssets(); +} diff --git a/apps/opencs/view/render/previewwidget.hpp b/apps/opencs/view/render/previewwidget.hpp index 73f7dc810..630ccf293 100644 --- a/apps/opencs/view/render/previewwidget.hpp +++ b/apps/opencs/view/render/previewwidget.hpp @@ -47,6 +47,8 @@ namespace CSVRender void referenceDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); void referenceAboutToBeRemoved (const QModelIndex& parent, int start, int end); + + void assetTablesChanged (); }; } diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index 82eebf127..11c7f5926 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -221,8 +221,8 @@ SceneWidget::SceneWidget(std::shared_ptr resourceSyste SceneWidget::~SceneWidget() { - // Since we're holding on to the scene templates past the existence of this graphics context, we'll need to manually release the created objects - mResourceSystem->getSceneManager()->releaseGLObjects(mView->getCamera()->getGraphicsContext()->getState()); + // Since we're holding on to the resources past the existence of this graphics context, we'll need to manually release the created objects + mResourceSystem->releaseGLObjects(mView->getCamera()->getGraphicsContext()->getState()); } void SceneWidget::setLighting(Lighting *lighting) @@ -393,6 +393,7 @@ void SceneWidget::selectNavigationMode (const std::string& mode) mCurrentCamControl->setCamera(NULL); mCurrentCamControl = mOrbitCamControl; mOrbitCamControl->setCamera(getCamera()); + mOrbitCamControl->reset(); } } diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index b82aa45b2..3201f7303 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -47,6 +47,9 @@ CSVRender::UnpagedWorldspaceWidget::UnpagedWorldspaceWidget (const std::string& connect (mCellsModel, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), this, SLOT (cellRowsAboutToBeRemoved (const QModelIndex&, int, int))); + connect (&document.getData(), SIGNAL (assetTablesChanged ()), + this, SLOT (assetTablesChanged ())); + update(); mCell.reset (new Cell (document.getData(), mRootNode, mCellId)); @@ -82,6 +85,12 @@ void CSVRender::UnpagedWorldspaceWidget::cellRowsAboutToBeRemoved (const QModelI emit closeRequest(); } +void CSVRender::UnpagedWorldspaceWidget::assetTablesChanged() +{ + if (mCell) + mCell->reloadAssets(); +} + bool CSVRender::UnpagedWorldspaceWidget::handleDrop (const std::vector& universalIdData, DropType type) { if (WorldspaceWidget::handleDrop (universalIdData, type)) diff --git a/apps/opencs/view/render/unpagedworldspacewidget.hpp b/apps/opencs/view/render/unpagedworldspacewidget.hpp index 5283b3a97..527463990 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.hpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.hpp @@ -108,6 +108,8 @@ namespace CSVRender void cellRowsAboutToBeRemoved (const QModelIndex& parent, int start, int end); + void assetTablesChanged (); + signals: void cellChanged(const CSMWorld::UniversalId& id); diff --git a/apps/opencs/view/world/infocreator.cpp b/apps/opencs/view/world/infocreator.cpp index f68c69094..2f1615c87 100644 --- a/apps/opencs/view/world/infocreator.cpp +++ b/apps/opencs/view/world/infocreator.cpp @@ -32,13 +32,19 @@ std::string CSVWorld::InfoCreator::getId() const void CSVWorld::InfoCreator::configureCreateCommand (CSMWorld::CreateCommand& command) const { - int index = - dynamic_cast (*getData().getTableModel (getCollectionId())). - findColumnIndex ( - getCollectionId().getType()==CSMWorld::UniversalId::Type_TopicInfos ? - CSMWorld::Columns::ColumnId_Topic : CSMWorld::Columns::ColumnId_Journal); + CSMWorld::IdTable& table = dynamic_cast (*getData().getTableModel (getCollectionId())); - command.addValue (index, mTopic->text()); + if (getCollectionId() == CSMWorld::UniversalId::Type_TopicInfos) + { + command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Topic), mTopic->text()); + command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Rank), -1); + command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Gender), -1); + command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_PcRank), -1); + } + else + { + command.addValue (table.findColumnIndex(CSMWorld::Columns::ColumnId_Journal), mTopic->text()); + } } CSVWorld::InfoCreator::InfoCreator (CSMWorld::Data& data, QUndoStack& undoStack, diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index c7886eed1..5072cacda 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -113,7 +113,6 @@ void OMW::Engine::frame(float frametime) try { mStartTick = mViewer->getStartTick(); - mEnvironment.setFrameDuration (frametime); // update input mEnvironment.getInputManager()->update(frametime, false); @@ -378,8 +377,7 @@ void OMW::Engine::setResourceDir (const boost::filesystem::path& parResDir) mResDir = parResDir; } -// Set start cell name (only interiors for now) - +// Set start cell name void OMW::Engine::setCell (const std::string& cellName) { mCellName = cellName; @@ -763,6 +761,8 @@ void OMW::Engine::go() Settings::Manager::getString("screenshot format", "General"))); mViewer->addEventHandler(mScreenCaptureHandler); + mEnvironment.setFrameRateLimit(Settings::Manager::getFloat("framerate limit", "Video")); + // Create encoder ToUTF8::Utf8Encoder encoder (mEncoding); mEncoder = &encoder; @@ -816,7 +816,6 @@ void OMW::Engine::go() // Start the main rendering loop osg::Timer frameTimer; double simulationTime = 0.0; - float framerateLimit = Settings::Manager::getFloat("framerate limit", "Video"); while (!mViewer->done() && !mEnvironment.getStateManager()->hasQuitRequest()) { double dt = frameTimer.time_s(); @@ -841,6 +840,8 @@ void OMW::Engine::go() mViewer->advance(simulationTime); + mEnvironment.setFrameDuration(dt); + frame(dt); if (!mEnvironment.getInputManager()->isWindowVisible()) @@ -858,15 +859,7 @@ void OMW::Engine::go() mViewer->renderingTraversals(); } - if (framerateLimit > 0.f) - { - double thisFrameTime = frameTimer.time_s(); - double minFrameTime = 1.0 / framerateLimit; - if (thisFrameTime < minFrameTime) - { - OpenThreads::Thread::microSleep(1000*1000*(minFrameTime-thisFrameTime)); - } - } + mEnvironment.limitFrameRate(frameTimer.time_s()); } // Save user settings diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index 29419a4c2..bf144bfed 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -148,7 +148,7 @@ namespace OMW /// Set resource dir void setResourceDir(const boost::filesystem::path& parResDir); - /// Set start cell name (only interiors for now) + /// Set start cell name void setCell(const std::string& cellName); /** diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index b1abdd4af..1421930e2 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -30,6 +30,9 @@ #include #endif +#if (defined(__APPLE__) || defined(__linux) || defined(__unix) || defined(__posix)) +#include +#endif #if (defined(__APPLE__) || (defined(__linux) && !defined(ANDROID)) || (defined(__unix) && !defined(ANDROID)) || defined(__posix)) #define USE_CRASH_CATCHER 1 diff --git a/apps/openmw/mwbase/environment.cpp b/apps/openmw/mwbase/environment.cpp index 4efa7c273..5d01525b9 100644 --- a/apps/openmw/mwbase/environment.cpp +++ b/apps/openmw/mwbase/environment.cpp @@ -2,6 +2,8 @@ #include +#include + #include "world.hpp" #include "scriptmanager.hpp" #include "dialoguemanager.hpp" @@ -17,7 +19,7 @@ MWBase::Environment *MWBase::Environment::sThis = 0; MWBase::Environment::Environment() : mWorld (0), mSoundManager (0), mScriptManager (0), mWindowManager (0), mMechanicsManager (0), mDialogueManager (0), mJournal (0), mInputManager (0), mStateManager (0), - mFrameDuration (0) + mFrameDuration (0), mFrameRateLimit(0.f) { assert (!sThis); sThis = this; @@ -79,6 +81,29 @@ void MWBase::Environment::setFrameDuration (float duration) mFrameDuration = duration; } +void MWBase::Environment::setFrameRateLimit(float limit) +{ + mFrameRateLimit = limit; +} + +float MWBase::Environment::getFrameRateLimit() const +{ + return mFrameRateLimit; +} + +void MWBase::Environment::limitFrameRate(double dt) const +{ + if (mFrameRateLimit > 0.f) + { + double thisFrameTime = dt; + double minFrameTime = 1.0 / static_cast(mFrameRateLimit); + if (thisFrameTime < minFrameTime) + { + OpenThreads::Thread::microSleep(1000*1000*(minFrameTime-thisFrameTime)); + } + } +} + MWBase::World *MWBase::Environment::getWorld() const { assert (mWorld); diff --git a/apps/openmw/mwbase/environment.hpp b/apps/openmw/mwbase/environment.hpp index 7f7919f81..9163b21f3 100644 --- a/apps/openmw/mwbase/environment.hpp +++ b/apps/openmw/mwbase/environment.hpp @@ -33,6 +33,7 @@ namespace MWBase InputManager *mInputManager; StateManager *mStateManager; float mFrameDuration; + float mFrameRateLimit; Environment (const Environment&); ///< not implemented @@ -67,6 +68,10 @@ namespace MWBase void setFrameDuration (float duration); ///< Set length of current frame in seconds. + void setFrameRateLimit(float frameRateLimit); + float getFrameRateLimit() const; + void limitFrameRate(double dt) const; + World *getWorld() const; SoundManager *getSoundManager() const; diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 84063247d..9a65a7bc9 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -7,6 +7,8 @@ #include #include +#include "../mwworld/ptr.hpp" + namespace osg { class Vec3f; @@ -231,6 +233,7 @@ namespace MWBase virtual void keepPlayerAlive() = 0; virtual bool isReadyToBlock (const MWWorld::Ptr& ptr) const = 0; + virtual bool isAttackingOrSpell(const MWWorld::Ptr &ptr) const = 0; virtual void confiscateStolenItems (const MWWorld::Ptr& player, const MWWorld::Ptr& targetContainer) = 0; @@ -240,8 +243,8 @@ namespace MWBase /// Has the player stolen this item from the given owner? virtual bool isItemStolenFrom(const std::string& itemid, const std::string& ownerid) = 0; - - virtual bool isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::CellRef& cellref, MWWorld::Ptr& victim) = 0; + + virtual bool isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::ConstPtr& item, MWWorld::Ptr& victim) = 0; /// Turn actor into werewolf or normal form. virtual void setWerewolf(const MWWorld::Ptr& actor, bool werewolf) = 0; @@ -251,6 +254,11 @@ namespace MWBase virtual void applyWerewolfAcrobatics(const MWWorld::Ptr& actor) = 0; virtual void cleanupSummonedCreature(const MWWorld::Ptr& caster, int creatureActorId) = 0; + + virtual void confiscateStolenItemToOwner(const MWWorld::Ptr &player, const MWWorld::Ptr &item, const MWWorld::Ptr& victim, int count) = 0; + + virtual bool isRunning(const MWWorld::Ptr& ptr) = 0; + virtual bool isSneaking(const MWWorld::Ptr& ptr) = 0; }; } diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 6829cddf3..aa4cdf22c 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -222,7 +222,10 @@ namespace MWBase virtual void setSpellVisibility(bool visible) = 0; virtual void setSneakVisibility(bool visible) = 0; - virtual void activateQuickKey (int index) = 0; + /// activate selected quick key + virtual void activateQuickKey (int index) = 0; + /// update activated quick key state (if action executing was delayed for some reason) + virtual void updateActivatedQuickKey () = 0; virtual std::string getSelectedSpell() = 0; virtual void setSelectedSpell(const std::string& spellId, int successChancePercent) = 0; diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index 4910ab932..8df262f24 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -51,6 +51,11 @@ namespace MWClass return ""; } + bool Activator::isActivator() const + { + return true; + } + bool Activator::useAnim() const { return true; diff --git a/apps/openmw/mwclass/activator.hpp b/apps/openmw/mwclass/activator.hpp index 21afa7b8e..3f333f4cb 100644 --- a/apps/openmw/mwclass/activator.hpp +++ b/apps/openmw/mwclass/activator.hpp @@ -42,6 +42,8 @@ namespace MWClass virtual bool useAnim() const; ///< Whether or not to use animated variant of model (default false) + + virtual bool isActivator() const; }; } diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index a6a0d8181..5edf9ff0f 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -4,6 +4,7 @@ #include #include +#include /* Start of tes3mp addition @@ -558,10 +559,27 @@ namespace MWClass return action; } - if(getCreatureStats(ptr).isDead()) - return std::shared_ptr(new MWWorld::ActionOpen(ptr, true)); - if(ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat()) + const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); + + if(stats.isDead()) + { + bool canLoot = Settings::Manager::getBool ("can loot during death animation", "Game"); + + // by default user can loot friendly actors during death animation + if (canLoot && !stats.getAiSequence().isInCombat()) + return std::shared_ptr(new MWWorld::ActionOpen(ptr, true)); + + // otherwise wait until death animation + if(stats.isDeathAnimationFinished()) + return std::shared_ptr(new MWWorld::ActionOpen(ptr, true)); + + // death animation is not finished, do nothing + return std::shared_ptr (new MWWorld::FailedAction("")); + } + + if(stats.getAiSequence().isInCombat()) return std::shared_ptr(new MWWorld::FailedAction("")); + return std::shared_ptr(new MWWorld::ActionTalk(ptr)); } @@ -668,7 +686,11 @@ namespace MWClass return true; const CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData(); - return !customData.mCreatureStats.getAiSequence().isInCombat() || customData.mCreatureStats.isDead(); + + if (customData.mCreatureStats.isDead() && customData.mCreatureStats.isDeathAnimationFinished()) + return true; + + return !customData.mCreatureStats.getAiSequence().isInCombat(); } MWGui::ToolTipInfo Creature::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 056833731..5b03c626f 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -88,6 +88,11 @@ namespace MWClass } } + bool Door::isDoor() const + { + return true; + } + bool Door::useAnim() const { return true; diff --git a/apps/openmw/mwclass/door.hpp b/apps/openmw/mwclass/door.hpp index a2d129522..57e475382 100644 --- a/apps/openmw/mwclass/door.hpp +++ b/apps/openmw/mwclass/door.hpp @@ -20,6 +20,8 @@ namespace MWClass virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWPhysics::PhysicsSystem& physics) const; + virtual bool isDoor() const; + virtual bool useAnim() const; virtual std::string getName (const MWWorld::ConstPtr& ptr) const; diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 873815324..38da4887c 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -7,6 +7,7 @@ #include #include #include +#include /* Start of tes3mp addition @@ -999,16 +1000,35 @@ namespace MWClass return action; } - if(getCreatureStats(ptr).isDead()) - return std::shared_ptr(new MWWorld::ActionOpen(ptr, true)); - if(ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat()) - return std::shared_ptr(new MWWorld::FailedAction("#{sActorInCombat}")); + const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); + + if(stats.isDead()) + { + bool canLoot = Settings::Manager::getBool ("can loot during death animation", "Game"); + + // by default user can loot friendly actors during death animation + if (canLoot && !stats.getAiSequence().isInCombat()) + return std::shared_ptr(new MWWorld::ActionOpen(ptr, true)); + + // otherwise wait until death animation + if(stats.isDeathAnimationFinished()) + return std::shared_ptr(new MWWorld::ActionOpen(ptr, true)); + + // death animation is not finished, do nothing + return std::shared_ptr (new MWWorld::FailedAction("")); + } + + if(stats.getAiSequence().isInCombat()) + return std::shared_ptr(new MWWorld::FailedAction("")); + if(getCreatureStats(actor).getStance(MWMechanics::CreatureStats::Stance_Sneak) - || ptr.getClass().getCreatureStats(ptr).getKnockedDown()) + || ptr.getClass().getCreatureStats(ptr).getKnockedDown()) return std::shared_ptr(new MWWorld::ActionOpen(ptr)); // stealing + // Can't talk to werewolfs if(ptr.getClass().isNpc() && ptr.getClass().getNpcStats(ptr).isWerewolf()) return std::shared_ptr (new MWWorld::FailedAction("")); + return std::shared_ptr(new MWWorld::ActionTalk(ptr)); } @@ -1155,7 +1175,11 @@ namespace MWClass return true; const NpcCustomData& customData = ptr.getRefData().getCustomData()->asNpcCustomData(); - return !customData.mNpcStats.getAiSequence().isInCombat() || customData.mNpcStats.isDead(); + + if (customData.mNpcStats.isDead() && customData.mNpcStats.isDeathAnimationFinished()) + return true; + + return !customData.mNpcStats.getAiSequence().isInCombat(); } MWGui::ToolTipInfo Npc::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index efb6248af..62a9b6d0f 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -4,6 +4,7 @@ #include #include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" @@ -373,6 +374,9 @@ namespace MWClass if (hasItemHealth(ptr) && ptr.getCellRef().getCharge() == 0) return std::make_pair(0, "#{sInventoryMessage1}"); + if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(npc)) + return std::make_pair(0, "#{sCantEquipWeapWarning}"); + std::pair, bool> slots_ = ptr.getClass().getEquipmentSlots(ptr); if (slots_.first.empty()) diff --git a/apps/openmw/mwgui/bookpage.cpp b/apps/openmw/mwgui/bookpage.cpp index 20d3448b5..137594076 100644 --- a/apps/openmw/mwgui/bookpage.cpp +++ b/apps/openmw/mwgui/bookpage.cpp @@ -435,9 +435,15 @@ struct TypesetBookImpl::Typesetter : BookTypesetter } else { + // The section won't completely fit on the current page. Finish the current page and start a new one. + mBook->mPages.push_back (Page (curPageStart, curPageStop)); + + curPageStart = i->mRect.top; + curPageStop = i->mRect.bottom; + //split section int sectionHeightLeft = sectionHeight; - while (sectionHeightLeft > mPageHeight) + while (sectionHeightLeft >= mPageHeight) { // Adjust to the top of the first line that does not fit on the current page anymore int splitPos = curPageStop; diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp index 41c7446a7..4e4f6ab3b 100644 --- a/apps/openmw/mwgui/enchantingdialog.cpp +++ b/apps/openmw/mwgui/enchantingdialog.cpp @@ -360,8 +360,9 @@ namespace MWGui if (msg.find("%s") != std::string::npos) msg.replace(msg.find("%s"), 2, item.getClass().getName(item)); MWBase::Environment::get().getWindowManager()->messageBox(msg); - MWBase::Environment::get().getMechanicsManager()->commitCrime(player, mPtr, MWBase::MechanicsManager::OT_Theft, - item.getClass().getValue(item), true); + + MWBase::Environment::get().getMechanicsManager()->confiscateStolenItemToOwner(player, item, mPtr, 1); + MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Enchanting); MWBase::Environment::get().getDialogueManager()->goodbyeSelected(); return; diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index 9abd91a85..9fd6d3ed2 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -219,29 +219,33 @@ namespace MWGui void HUD::setValue(const std::string& id, const MWMechanics::DynamicStat& value) { - int current = std::max(0, static_cast(value.getCurrent())); + int current = static_cast(value.getCurrent()); int modified = static_cast(value.getModified()); + // Fatigue can be negative + if (id != "FBar") + current = std::max(0, current); + MyGUI::Widget* w; std::string valStr = MyGUI::utility::toString(current) + " / " + MyGUI::utility::toString(modified); if (id == "HBar") { - mHealth->setProgressRange(modified); - mHealth->setProgressPosition(current); + mHealth->setProgressRange(std::max(0, modified)); + mHealth->setProgressPosition(std::max(0, current)); getWidget(w, "HealthFrame"); w->setUserString("Caption_HealthDescription", "#{sHealthDesc}\n" + valStr); } else if (id == "MBar") { - mMagicka->setProgressRange (modified); - mMagicka->setProgressPosition (current); + mMagicka->setProgressRange(std::max(0, modified)); + mMagicka->setProgressPosition(std::max(0, current)); getWidget(w, "MagickaFrame"); w->setUserString("Caption_HealthDescription", "#{sMagDesc}\n" + valStr); } else if (id == "FBar") { - mStamina->setProgressRange (modified); - mStamina->setProgressPosition (current); + mStamina->setProgressRange(std::max(0, modified)); + mStamina->setProgressPosition(std::max(0, current)); getWidget(w, "FatigueFrame"); w->setUserString("Caption_HealthDescription", "#{sFatDesc}\n" + valStr); } diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index a44dc142f..a35bed8a5 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -43,6 +43,7 @@ #include "../mwrender/characterpreview.hpp" #include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/creaturestats.hpp" #include "itemview.hpp" #include "inventoryitemmodel.hpp" @@ -693,9 +694,18 @@ namespace MWGui void InventoryWindow::cycle(bool next) { + MWWorld::Ptr player = MWMechanics::getPlayer(); + + if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(player)) + return; + + const MWMechanics::CreatureStats &stats = player.getClass().getCreatureStats(player); + if (stats.isParalyzed() || stats.getKnockedDown() || stats.isDead() || stats.getHitRecovery()) + return; + ItemModel::ModelIndex selected = -1; // not using mSortFilterModel as we only need sorting, not filtering - SortFilterItemModel model(new InventoryItemModel(MWMechanics::getPlayer())); + SortFilterItemModel model(new InventoryItemModel(player)); model.setSortByType(false); model.update(); if (model.getItemCount() == 0) diff --git a/apps/openmw/mwgui/jailscreen.cpp b/apps/openmw/mwgui/jailscreen.cpp index 14b715c7f..0874f5fdf 100644 --- a/apps/openmw/mwgui/jailscreen.cpp +++ b/apps/openmw/mwgui/jailscreen.cpp @@ -136,7 +136,7 @@ namespace MWGui */ value.setBase(std::min(100, value.getBase()+1)); else - value.setBase(value.getBase()-1); + value.setBase(std::max(0, value.getBase()-1)); } const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); diff --git a/apps/openmw/mwgui/journalbooks.cpp b/apps/openmw/mwgui/journalbooks.cpp index 5634eb080..a1d74fab6 100644 --- a/apps/openmw/mwgui/journalbooks.cpp +++ b/apps/openmw/mwgui/journalbooks.cpp @@ -82,7 +82,7 @@ namespace AddEntry::operator () (entry); - mTypesetter->sectionBreak (10); + mTypesetter->sectionBreak (30); } }; @@ -107,7 +107,7 @@ namespace mTypesetter->selectContent (mContentId); mTypesetter->write (mBodyStyle, 2, 3);// end quote - mTypesetter->sectionBreak (10); + mTypesetter->sectionBreak (30); } }; @@ -121,7 +121,7 @@ namespace void operator () (MWGui::JournalViewModel::Utf8Span topicName) { mTypesetter->write (mBodyStyle, topicName); - mTypesetter->sectionBreak (10); + mTypesetter->sectionBreak (); } }; @@ -135,7 +135,7 @@ namespace void operator () (MWGui::JournalViewModel::Utf8Span topicName) { mTypesetter->write (mBodyStyle, topicName); - mTypesetter->sectionBreak (10); + mTypesetter->sectionBreak (); } }; } @@ -250,7 +250,7 @@ book JournalBooks::createTopicIndexBook () BookTypesetter::Ptr JournalBooks::createTypesetter () { //TODO: determine page size from layout... - return BookTypesetter::create (240, 300); + return BookTypesetter::create (240, 320); } } diff --git a/apps/openmw/mwgui/journalwindow.cpp b/apps/openmw/mwgui/journalwindow.cpp index 105f95085..e96cc8b70 100644 --- a/apps/openmw/mwgui/journalwindow.cpp +++ b/apps/openmw/mwgui/journalwindow.cpp @@ -187,12 +187,12 @@ namespace } adjustButton(TopicsBTN); - int width = getWidget(TopicsBTN)->getSize().width + getWidget(QuestsBTN)->getSize().width; int topicsWidth = getWidget(TopicsBTN)->getSize().width; - int pageWidth = getWidget(RightBookPage)->getSize().width; + int cancelLeft = getWidget(CancelBTN)->getPosition().left; + int cancelRight = getWidget(CancelBTN)->getPosition().left + getWidget(CancelBTN)->getSize().width; - getWidget(TopicsBTN)->setPosition((pageWidth - width)/2, getWidget(TopicsBTN)->getPosition().top); - getWidget(QuestsBTN)->setPosition((pageWidth - width)/2 + topicsWidth, getWidget(QuestsBTN)->getPosition().top); + getWidget(TopicsBTN)->setPosition(cancelLeft - topicsWidth, getWidget(TopicsBTN)->getPosition().top); + getWidget(QuestsBTN)->setPosition(cancelRight, getWidget(QuestsBTN)->getPosition().top); mQuestMode = false; mAllQuests = false; diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index ca6a0b0a4..c5836b653 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -102,6 +102,15 @@ namespace MWGui mBackgroundImage->setVisible(visible); } + double LoadingScreen::getTargetFrameRate() const + { + double frameRateLimit = MWBase::Environment::get().getFrameRateLimit(); + if (frameRateLimit > 0) + return std::min(frameRateLimit, mTargetFrameRate); + else + return mTargetFrameRate; + } + class CopyFramebufferToTextureCallback : public osg::Camera::DrawCallback { public: @@ -141,7 +150,7 @@ namespace MWGui if (mViewer->getIncrementalCompileOperation()) { mViewer->getIncrementalCompileOperation()->setMaximumNumOfObjectsToCompilePerFrame(100); - mViewer->getIncrementalCompileOperation()->setTargetFrameRate(mTargetFrameRate); + mViewer->getIncrementalCompileOperation()->setTargetFrameRate(getTargetFrameRate()); } // Assign dummy bounding sphere callback to avoid the bounding sphere of the entire scene being recomputed after each frame of loading @@ -210,7 +219,7 @@ namespace MWGui void LoadingScreen::setProgress (size_t value) { // skip expensive update if there isn't enough visible progress - if (value - mProgress < mProgressBar->getScrollRange()/200.f) + if (mProgressBar->getWidth() <= 0 || value - mProgress < mProgressBar->getScrollRange()/mProgressBar->getWidth()) return; value = std::min(value, mProgressBar->getScrollRange()-1); mProgress = value; @@ -231,7 +240,7 @@ namespace MWGui bool LoadingScreen::needToDrawLoadingScreen() { - if ( mTimer.time_m() <= mLastRenderTime + (1.0/mTargetFrameRate) * 1000.0) + if ( mTimer.time_m() <= mLastRenderTime + (1.0/getTargetFrameRate()) * 1000.0) return false; // the minimal delay before a loading screen shows diff --git a/apps/openmw/mwgui/loadingscreen.hpp b/apps/openmw/mwgui/loadingscreen.hpp index 100c17e11..2f8831fdc 100644 --- a/apps/openmw/mwgui/loadingscreen.hpp +++ b/apps/openmw/mwgui/loadingscreen.hpp @@ -43,6 +43,8 @@ namespace MWGui virtual void setVisible(bool visible); + double getTargetFrameRate() const; + private: void findSplashScreens(); bool needToDrawLoadingScreen(); @@ -73,8 +75,6 @@ namespace MWGui std::vector mSplashScreens; - // TODO: add releaseGLObjects() for mTexture - osg::ref_ptr mTexture; std::unique_ptr mGuiTexture; diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index f8ddeba3e..ab43df0f1 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -196,7 +196,7 @@ namespace MWGui InteractiveMessageBox::InteractiveMessageBox(MessageBoxManager& parMessageBoxManager, const std::string& message, const std::vector& buttons) - : WindowModal("openmw_interactive_messagebox.layout") + : WindowModal(MWBase::Environment::get().getWindowManager()->isGuiMode() ? "openmw_interactive_messagebox_notransp.layout" : "openmw_interactive_messagebox.layout") , mMessageBoxManager(parMessageBoxManager) , mButtonPressed(-1) { diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 619540cff..4e4462409 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -14,6 +14,7 @@ #include "../mwworld/esmstore.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" @@ -36,6 +37,7 @@ namespace MWGui , mItemSelectionDialog(0) , mMagicSelectionDialog(0) , mSelectedIndex(-1) + , mActivatedIndex(-1) { getWidget(mOkButton, "OKButton"); getWidget(mInstructionLabel, "InstructionLabel"); @@ -69,6 +71,8 @@ namespace MWGui void QuickKeysMenu::clear() { + mActivatedIndex = -1; + for (int i=0; i<10; ++i) { unassign(mQuickKeyButtons[i], i); @@ -254,6 +258,15 @@ namespace MWGui mMagicSelectionDialog->setVisible(false); } + void QuickKeysMenu::updateActivatedQuickKey() + { + // there is no delayed action, nothing to do. + if (mActivatedIndex < 0) + return; + + activateQuickKey(mActivatedIndex); + } + void QuickKeysMenu::activateQuickKey(int index) { assert (index-1 >= 0); @@ -263,6 +276,27 @@ namespace MWGui MWWorld::Ptr player = MWMechanics::getPlayer(); MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); + const MWMechanics::CreatureStats &playerStats = player.getClass().getCreatureStats(player); + + // Delay action executing, + // if player is busy for now (casting a spell, attacking someone, etc.) + bool isDelayNeeded = MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(player) + || playerStats.getKnockedDown() + || playerStats.getHitRecovery(); + + bool isReturnNeeded = playerStats.isParalyzed() || playerStats.isDead(); + if (isReturnNeeded && type != Type_Item) + { + return; + } + + if (isDelayNeeded && type != Type_Item) + { + mActivatedIndex = index; + return; + } + else + mActivatedIndex = -1; if (type == Type_Item || type == Type_MagicItem) { @@ -309,6 +343,21 @@ namespace MWGui else if (type == Type_Item) { MWWorld::Ptr item = *button->getUserData(); + bool isWeapon = item.getTypeName() == typeid(ESM::Weapon).name(); + + // delay weapon switching if player is busy + if (isDelayNeeded && isWeapon) + { + mActivatedIndex = index; + return; + } + + // disable weapon switching if player is dead or paralyzed + if (isReturnNeeded && isWeapon) + { + return; + } + MWBase::Environment::get().getWindowManager()->useItem(item); MWWorld::ConstContainerStoreIterator rightHand = store.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); // change draw state only if the item is in player's right hand diff --git a/apps/openmw/mwgui/quickkeysmenu.hpp b/apps/openmw/mwgui/quickkeysmenu.hpp index afbcff001..64db9043e 100644 --- a/apps/openmw/mwgui/quickkeysmenu.hpp +++ b/apps/openmw/mwgui/quickkeysmenu.hpp @@ -36,6 +36,7 @@ namespace MWGui void onAssignMagicCancel (); void activateQuickKey(int index); + void updateActivatedQuickKey(); /// @note This enum is serialized, so don't move the items around! enum QuickKeyType @@ -64,7 +65,7 @@ namespace MWGui MagicSelectionDialog* mMagicSelectionDialog; int mSelectedIndex; - + int mActivatedIndex; void onQuickKeyButtonClicked(MyGUI::Widget* sender); void onOkButtonClicked(MyGUI::Widget* sender); diff --git a/apps/openmw/mwgui/referenceinterface.cpp b/apps/openmw/mwgui/referenceinterface.cpp index 76bb4f53f..9aaa98f19 100644 --- a/apps/openmw/mwgui/referenceinterface.cpp +++ b/apps/openmw/mwgui/referenceinterface.cpp @@ -20,9 +20,8 @@ namespace MWGui { MWWorld::CellStore* playerCell = MWMechanics::getPlayer().getCell(); - // check if player has changed cell, or count of the reference has become 0 - if ((playerCell != mCurrentPlayerCell && mCurrentPlayerCell != NULL) - || (!mPtr.isEmpty() && mPtr.getRefData().getCount() == 0)) + // check if count of the reference has become 0 + if (!mPtr.isEmpty() && mPtr.getRefData().getCount() == 0) { if (!mPtr.isEmpty()) { diff --git a/apps/openmw/mwgui/review.cpp b/apps/openmw/mwgui/review.cpp index 1a680b801..bf18e7355 100644 --- a/apps/openmw/mwgui/review.cpp +++ b/apps/openmw/mwgui/review.cpp @@ -180,7 +180,7 @@ namespace MWGui void ReviewDialog::setFatigue(const MWMechanics::DynamicStat& value) { - int current = std::max(0, static_cast(value.getCurrent())); + int current = static_cast(value.getCurrent()); int modified = static_cast(value.getModified()); mFatigue->setValue(current, modified); diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp index 5f1df4163..0ae16fffa 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -20,6 +20,7 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/class.hpp" @@ -216,6 +217,15 @@ namespace MWGui void SpellWindow::cycle(bool next) { + MWWorld::Ptr player = MWMechanics::getPlayer(); + + if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(player)) + return; + + const MWMechanics::CreatureStats &stats = player.getClass().getCreatureStats(player); + if (stats.isParalyzed() || stats.getKnockedDown() || stats.isDead() || stats.getHitRecovery()) + return; + mSpellView->setModel(new SpellModel(MWMechanics::getPlayer())); SpellModel::ModelIndex selected = 0; diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index 22140b8b2..17e51e338 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -102,12 +102,13 @@ namespace MWGui { MyGUI::ProgressBar* pt; getWidget(pt, name); - pt->setProgressRange(max); - pt->setProgressPosition(val); std::stringstream out; out << val << "/" << max; setText(tname, out.str().c_str()); + + pt->setProgressRange(std::max(0, max)); + pt->setProgressPosition(std::max(0, val)); } void StatsWindow::setPlayerName(const std::string& playerName) @@ -147,9 +148,13 @@ namespace MWGui void StatsWindow::setValue (const std::string& id, const MWMechanics::DynamicStat& value) { - int current = std::max(0, static_cast(value.getCurrent())); + int current = static_cast(value.getCurrent()); int modified = static_cast(value.getModified()); + // Fatigue can be negative + if (id != "FBar") + current = std::max(0, current); + setBar (id, id + "T", current, modified); // health, magicka, fatigue tooltip diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 1e770a60b..9b89c3957 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -359,12 +359,11 @@ namespace MWGui { if(!mFocusObject.isEmpty()) { - const MWWorld::CellRef& cellref = mFocusObject.getCellRef(); MWWorld::Ptr ptr = MWMechanics::getPlayer(); MWWorld::Ptr victim; MWBase::MechanicsManager* mm = MWBase::Environment::get().getMechanicsManager(); - bool allowed = mm->isAllowedToUse(ptr, cellref, victim); + bool allowed = mm->isAllowedToUse(ptr, mFocusObject, victim); return !allowed; } @@ -378,17 +377,10 @@ namespace MWGui { mDynamicToolTipBox->setVisible(true); - if(mShowOwned == 1 || mShowOwned == 3) - { - if(isFocusObject && checkOwned()) - { - mDynamicToolTipBox->changeWidgetSkin("HUD_Box_NoTransp_Owned"); - } - else - { - mDynamicToolTipBox->changeWidgetSkin("HUD_Box_NoTransp"); - } - } + if((mShowOwned == 1 || mShowOwned == 3) && isFocusObject && checkOwned()) + mDynamicToolTipBox->changeWidgetSkin(MWBase::Environment::get().getWindowManager()->isGuiMode() ? "HUD_Box_NoTransp_Owned" : "HUD_Box_Owned"); + else + mDynamicToolTipBox->changeWidgetSkin(MWBase::Environment::get().getWindowManager()->isGuiMode() ? "HUD_Box_NoTransp" : "HUD_Box"); std::string caption = info.caption; std::string image = info.icon; diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index f82d2f68d..ca0bb48e8 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -311,9 +311,9 @@ namespace MWGui if (msg.find("%s") != std::string::npos) msg.replace(msg.find("%s"), 2, it->mBase.getClass().getName(it->mBase)); MWBase::Environment::get().getWindowManager()->messageBox(msg); - MWBase::Environment::get().getMechanicsManager()->commitCrime(player, mPtr, MWBase::MechanicsManager::OT_Theft, - it->mBase.getClass().getValue(it->mBase) - * it->mCount, true); + + MWBase::Environment::get().getMechanicsManager()->confiscateStolenItemToOwner(player, it->mBase, mPtr, it->mCount); + onCancelButtonClicked(mCancelButton); MWBase::Environment::get().getDialogueManager()->goodbyeSelected(); return; diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp index 8685475a4..ba58a9c69 100644 --- a/apps/openmw/mwgui/waitdialog.cpp +++ b/apps/openmw/mwgui/waitdialog.cpp @@ -134,7 +134,7 @@ namespace MWGui void WaitDialog::startWaiting(int hoursToWait) { - if(Settings::Manager::getBool("autosave","Saves") && mSleeping) //autosaves when enabled and sleeping + if(Settings::Manager::getBool("autosave","Saves")) //autosaves when enabled MWBase::Environment::get().getStateManager()->quickSave("Autosave"); MWBase::World* world = MWBase::Environment::get().getWorld(); diff --git a/apps/openmw/mwgui/widgets.cpp b/apps/openmw/mwgui/widgets.cpp index 695337cde..744ef236f 100644 --- a/apps/openmw/mwgui/widgets.cpp +++ b/apps/openmw/mwgui/widgets.cpp @@ -502,11 +502,10 @@ namespace MWGui if (mBarWidget) { - mBarWidget->setProgressRange(mMax); - mBarWidget->setProgressPosition(mValue); + mBarWidget->setProgressRange(std::max(0, mMax)); + mBarWidget->setProgressPosition(std::max(0, mValue)); } - if (mBarTextWidget) { std::stringstream out; diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 4db38b78f..73acd3c2f 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -529,6 +529,8 @@ namespace MWGui cleanupGarbage(); mHud->update(); + + updateActivatedQuickKey (); } void WindowManager::updateVisible() @@ -916,19 +918,30 @@ namespace MWGui if (block) { + osg::Timer frameTimer; while (mMessageBoxManager->readPressedButton(false) == -1 && !MWBase::Environment::get().getStateManager()->hasQuitRequest()) { - mMessageBoxManager->onFrame(0.f); - MWBase::Environment::get().getInputManager()->update(0, true, false); + double dt = frameTimer.time_s(); + frameTimer.setStartTick(); + + mMessageBoxManager->onFrame(dt); + MWBase::Environment::get().getInputManager()->update(dt, true, false); + if (!MWBase::Environment::get().getInputManager()->isWindowVisible()) + OpenThreads::Thread::microSleep(5000); + else + { + mViewer->eventTraversal(); + mViewer->updateTraversal(); + mViewer->renderingTraversals(); + } // at the time this function is called we are in the middle of a frame, // so out of order calls are necessary to get a correct frameNumber for the next frame. // refer to the advance() and frame() order in Engine::go() - mViewer->eventTraversal(); - mViewer->updateTraversal(); - mViewer->renderingTraversals(); mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); + + MWBase::Environment::get().limitFrameRate(frameTimer.time_s()); } } } @@ -1566,6 +1579,11 @@ namespace MWGui mHud->setCrosshairVisible (show && mCrosshairEnabled); } + void WindowManager::updateActivatedQuickKey () + { + mQuickKeysMenu->updateActivatedQuickKey(); + } + void WindowManager::activateQuickKey (int index) { mQuickKeysMenu->activateQuickKey(index); @@ -1869,18 +1887,28 @@ namespace MWGui if (mVideoWidget->hasAudioStream()) MWBase::Environment::get().getSoundManager()->pauseSounds( MWBase::SoundManager::Play_TypeMask&(~MWBase::SoundManager::Play_TypeMovie)); - + osg::Timer frameTimer; while (mVideoWidget->update() && !MWBase::Environment::get().getStateManager()->hasQuitRequest()) { - MWBase::Environment::get().getInputManager()->update(0, true, false); + double dt = frameTimer.time_s(); + frameTimer.setStartTick(); + MWBase::Environment::get().getInputManager()->update(dt, true, false); + + if (!MWBase::Environment::get().getInputManager()->isWindowVisible()) + OpenThreads::Thread::microSleep(5000); + else + { + mViewer->eventTraversal(); + mViewer->updateTraversal(); + mViewer->renderingTraversals(); + } // at the time this function is called we are in the middle of a frame, // so out of order calls are necessary to get a correct frameNumber for the next frame. // refer to the advance() and frame() order in Engine::go() - mViewer->eventTraversal(); - mViewer->updateTraversal(); - mViewer->renderingTraversals(); mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); + + MWBase::Environment::get().limitFrameRate(frameTimer.time_s()); } mVideoWidget->stop(); @@ -2021,12 +2049,8 @@ namespace MWGui char* text=0; text = SDL_GetClipboardText(); if (text) - { - // MyGUI's clipboard might still have color information, to retain that information, only set the new text - // if it actually changed (clipboard inserted by an external application) - if (MyGUI::TextIterator::getOnlyText(_data) != text) - _data = text; - } + _data = MyGUI::TextIterator::toTagsString(text); + SDL_free(text); } diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index e112cf186..de9898b34 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -251,7 +251,10 @@ namespace MWGui virtual void setSpellVisibility(bool visible); virtual void setSneakVisibility(bool visible); - virtual void activateQuickKey (int index); + /// activate selected quick key + virtual void activateQuickKey (int index); + /// update activated quick key state (if action executing was delayed for some reason) + virtual void updateActivatedQuickKey (); virtual std::string getSelectedSpell() { return mSelectedSpell; } virtual void setSelectedSpell(const std::string& spellId, int successChancePercent); diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 85ef09e45..c51c41483 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -32,6 +32,7 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/statemanager.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/player.hpp" #include "../mwworld/class.hpp" @@ -966,6 +967,9 @@ namespace MWInput inventory.getSelectedEnchantItem() == inventory.end()) return; + if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(mPlayer->getPlayer())) + return; + MWMechanics::DrawState_ state = mPlayer->getDrawState(); if (state == MWMechanics::DrawState_Weapon || state == MWMechanics::DrawState_Nothing) mPlayer->setDrawState(MWMechanics::DrawState_Spell); @@ -981,6 +985,9 @@ namespace MWInput if (!mControlSwitch["playerfighting"] || !mControlSwitch["playercontrols"]) return; + if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(mPlayer->getPlayer())) + return; + MWMechanics::DrawState_ state = mPlayer->getDrawState(); if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing) mPlayer->setDrawState(MWMechanics::DrawState_Weapon); @@ -1070,6 +1077,7 @@ namespace MWInput return; if(MWBase::Environment::get().getWindowManager()->getMode() != MWGui::GM_Journal + && MWBase::Environment::get().getWindowManager()->getMode() != MWGui::GM_MainMenu && MWBase::Environment::get().getWindowManager ()->getJournalAllowed()) { MWBase::Environment::get().getWindowManager()->playSound ("book open"); diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index 52c05fdfa..90d29f686 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -222,10 +222,23 @@ namespace MWMechanics } } - void ActiveSpells::purgeAll(float chance) + void ActiveSpells::purgeAll(float chance, bool spellOnly) { for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ) { + const std::string spellId = it->first; + + // if spellOnly is true, dispell only spells. Leave potions, enchanted items etc. + if (spellOnly) + { + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(spellId); + if (!spell || spell->mData.mType != ESM::Spell::ST_Spell) + { + ++it; + continue; + } + } + if (Misc::Rng::roll0to99() < chance) mSpells.erase(it++); else diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index 0f1f803b7..a19c8a51d 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -89,7 +89,7 @@ namespace MWMechanics void purgeEffect (short effectId, const std::string& sourceId); /// Remove all active effects, if roll succeeds (for each effect) - void purgeAll (float chance); + void purgeAll(float chance, bool spellOnly = false); /// Remove all effects with CASTER_LINKED flag that were cast by \a casterActorId void purge (int casterActorId); diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 98ea5e2a5..879798a44 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -832,7 +832,7 @@ namespace MWMechanics creatureStats.getActiveSpells().visitEffectSources(updateSummonedCreatures); if (ptr.getClass().hasInventoryStore(ptr)) ptr.getClass().getInventoryStore(ptr).visitEffectSources(updateSummonedCreatures); - updateSummonedCreatures.process(); + updateSummonedCreatures.process(mTimerDisposeSummonsCorpses == 0.f); } } @@ -851,6 +851,26 @@ namespace MWMechanics } } + bool Actors::isRunning(const MWWorld::Ptr& ptr) + { + PtrActorMap::iterator it = mActors.find(ptr); + if (it == mActors.end()) + return false; + CharacterController* ctrl = it->second->getCharacterController(); + + return ctrl->isRunning(); + } + + bool Actors::isSneaking(const MWWorld::Ptr& ptr) + { + PtrActorMap::iterator it = mActors.find(ptr); + if (it == mActors.end()) + return false; + CharacterController* ctrl = it->second->getCharacterController(); + + return ctrl->isSneaking(); + } + void Actors::updateDrowning(const MWWorld::Ptr& ptr, float duration) { PtrActorMap::iterator it = mActors.find(ptr); @@ -1080,7 +1100,9 @@ namespace MWMechanics } } - Actors::Actors() {} + Actors::Actors() { + mTimerDisposeSummonsCorpses = 0.2f; // We should add a delay between summoned creature death and its corpse despawning + } Actors::~Actors() { @@ -1149,6 +1171,7 @@ namespace MWMechanics // target lists get updated once every 1.0 sec if (timerUpdateAITargets >= 1.0f) timerUpdateAITargets = 0; if (timerUpdateHeadTrack >= 0.3f) timerUpdateHeadTrack = 0; + if (mTimerDisposeSummonsCorpses >= 0.2f) mTimerDisposeSummonsCorpses = 0; if (timerUpdateEquippedLight >= updateEquippedLightInterval) timerUpdateEquippedLight = 0; MWWorld::Ptr player = getPlayer(); @@ -1318,6 +1341,7 @@ namespace MWMechanics timerUpdateAITargets += duration; timerUpdateHeadTrack += duration; timerUpdateEquippedLight += duration; + mTimerDisposeSummonsCorpses += duration; // Looping magic VFX update // Note: we need to do this before any of the animations are updated. @@ -1955,6 +1979,16 @@ namespace MWMechanics return it->second->getCharacterController()->isReadyToBlock(); } + bool Actors::isAttackingOrSpell(const MWWorld::Ptr& ptr) const + { + PtrActorMap::const_iterator it = mActors.find(ptr); + if (it == mActors.end()) + return false; + CharacterController* ctrl = it->second->getCharacterController(); + + return ctrl->isAttackingOrSpell(); + } + void Actors::fastForwardAi() { if (!MWBase::Environment::get().getMechanicsManager()->isAIActive()) diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 0e59ce1ba..a73c405c8 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -117,6 +117,9 @@ namespace MWMechanics End of tes3mp addition */ + bool isRunning(const MWWorld::Ptr& ptr); + bool isSneaking(const MWWorld::Ptr& ptr); + void forceStateUpdate(const MWWorld::Ptr &ptr); bool playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number, bool persist=false); @@ -157,9 +160,11 @@ namespace MWMechanics void clear(); // Clear death counter bool isReadyToBlock(const MWWorld::Ptr& ptr) const; + bool isAttackingOrSpell(const MWWorld::Ptr& ptr) const; private: PtrActorMap mActors; + float mTimerDisposeSummonsCorpses; }; } diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index 1bfeff074..d44498966 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -71,6 +71,8 @@ namespace MWMechanics { const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find(mItem->getClass().getEnchantment(*mItem)); int types = getRangeTypes(enchantment->mEffects); + + isRanged = (types & RangeTypes::Target) | (types & RangeTypes::Self); return suggestCombatRange(types); } diff --git a/apps/openmw/mwmechanics/aicombataction.hpp b/apps/openmw/mwmechanics/aicombataction.hpp index dc6f359d6..466ae2dc4 100644 --- a/apps/openmw/mwmechanics/aicombataction.hpp +++ b/apps/openmw/mwmechanics/aicombataction.hpp @@ -54,7 +54,7 @@ namespace MWMechanics virtual float getCombatRange (bool& isRanged) const; /// Since this action has no animation, apply a small cool down for using it - virtual float getActionCooldown() { return 1.f; } + virtual float getActionCooldown() { return 0.75f; } }; class ActionPotion : public Action @@ -68,7 +68,7 @@ namespace MWMechanics virtual bool isAttackingOrSpell() const { return false; } /// Since this action has no animation, apply a small cool down for using it - virtual float getActionCooldown() { return 1.f; } + virtual float getActionCooldown() { return 0.75f; } }; class ActionWeapon : public Action diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index fe94246c4..fd5f9c7fe 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -22,8 +22,15 @@ struct AiFollowStorage : AiTemporaryBase { float mTimer; bool mMoving; - - AiFollowStorage() : mTimer(0.f), mMoving(false) {} + float mTargetAngleRadians; + bool mTurnActorToTarget; + + AiFollowStorage() : + mTimer(0.f), + mMoving(false), + mTargetAngleRadians(0.f), + mTurnActorToTarget(false) + {} }; int AiFollow::mFollowIndexCounter = 0; @@ -73,6 +80,15 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte AiFollowStorage& storage = state.get(); + bool& rotate = storage.mTurnActorToTarget; + if (rotate) + { + if (zTurn(actor, storage.mTargetAngleRadians)) + rotate = false; + + return false; + } + // AiFollow requires the target to be in range and within sight for the initial activation if (!mActive) { @@ -144,13 +160,33 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte //Set the target destination from the actor ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; - if (!storage.mMoving) - { - const short threshold = 10; // to avoid constant switching between moving/stopping + short baseFollowDistance = followDistance; + short threshold = 30; // to avoid constant switching between moving/stopping + if (storage.mMoving) + followDistance -= threshold; + else followDistance += threshold; + + osg::Vec3f targetPos(target.getRefData().getPosition().asVec3()); + osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3()); + + osg::Vec3f dir = targetPos - actorPos; + float targetDistSqr = dir.length2(); + + if (targetDistSqr <= followDistance * followDistance) + { + float faceAngleRadians = std::atan2(dir.x(), dir.y()); + + if (!zTurn(actor, faceAngleRadians, osg::DegreesToRadians(45.f))) + { + storage.mTargetAngleRadians = faceAngleRadians; + storage.mTurnActorToTarget = true; + } + + return false; } - storage.mMoving = !pathTo(actor, dest, duration, followDistance); // Go to the destination + storage.mMoving = !pathTo(actor, dest, duration, baseFollowDistance); // Go to the destination if (storage.mMoving) { diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index e6e3b4c4e..124468641 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -457,7 +457,9 @@ bool MWMechanics::Alchemy::knownEffect(unsigned int potionEffectIndex, const MWW static const float fWortChanceValue = MWBase::Environment::get().getWorld()->getStore().get().find("fWortChanceValue")->getFloat(); return (potionEffectIndex <= 1 && alchemySkill >= fWortChanceValue) - || (potionEffectIndex <= 3 && alchemySkill >= fWortChanceValue*2); + || (potionEffectIndex <= 3 && alchemySkill >= fWortChanceValue*2) + || (potionEffectIndex <= 5 && alchemySkill >= fWortChanceValue*3) + || (potionEffectIndex <= 7 && alchemySkill >= fWortChanceValue*4); } MWMechanics::Alchemy::Result MWMechanics::Alchemy::create (const std::string& name) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index bb8c12b8f..e7db2a501 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1768,7 +1768,7 @@ void CharacterController::update(float duration) mSecondsOfSwimming -= 1; } } - else if(isrunning) + else if(isrunning && !sneak) { mSecondsOfRunning += duration; while(mSecondsOfRunning > 1) @@ -1806,7 +1806,7 @@ void CharacterController::update(float duration) else fatigueLoss = fFatigueSwimRunBase + encumbrance * fFatigueSwimRunMult; } - if (isrunning) + else if (isrunning) fatigueLoss = fFatigueRunBase + encumbrance * fFatigueRunMult; } } @@ -2367,6 +2367,12 @@ bool CharacterController::isKnockedOut() const return mHitState == CharState_KnockOut; } +bool CharacterController::isAttackingOrSpell() const +{ + return mUpperBodyState != UpperCharState_Nothing && + mUpperBodyState != UpperCharState_WeapEquiped; +} + bool CharacterController::isSneaking() const { return mIdleState == CharState_IdleSneak || @@ -2376,6 +2382,18 @@ bool CharacterController::isSneaking() const mMovementState == CharState_SneakRight; } +bool CharacterController::isRunning() const +{ + return mMovementState == CharState_RunForward || + mMovementState == CharState_RunBack || + mMovementState == CharState_RunLeft || + mMovementState == CharState_RunRight || + mMovementState == CharState_SwimRunForward || + mMovementState == CharState_SwimRunBack || + mMovementState == CharState_SwimRunLeft || + mMovementState == CharState_SwimRunRight; +} + void CharacterController::setAttackingOrSpell(bool attackingOrSpell) { mAttackingOrSpell = attackingOrSpell; diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index a3cdf097d..bde64cdfb 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -266,6 +266,8 @@ public: bool isReadyToBlock() const; bool isKnockedOut() const; bool isSneaking() const; + bool isRunning() const; + bool isAttackingOrSpell() const; void setAttackingOrSpell(bool attackingOrSpell); void setAIAttackType(const std::string& attackType); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 10f0e3270..325173503 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -26,6 +26,7 @@ #include "../mwworld/inventorystore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" +#include "../mwworld/ptr.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -435,6 +436,16 @@ namespace MWMechanics mObjects.update(duration, paused); } + bool MechanicsManager::isRunning(const MWWorld::Ptr& ptr) + { + return mActors.isRunning(ptr); + } + + bool MechanicsManager::isSneaking(const MWWorld::Ptr& ptr) + { + return mActors.isSneaking(ptr); + } + void MechanicsManager::rest(bool sleep) { mActors.rest(sleep); @@ -845,8 +856,17 @@ namespace MWMechanics mAI = true; } - bool MechanicsManager::isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::CellRef& cellref, MWWorld::Ptr& victim) + bool MechanicsManager::isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::ConstPtr& item, MWWorld::Ptr& victim) { + const MWWorld::CellRef& cellref = item.getCellRef(); + // there is no harm to use unlocked doors + if (item.getClass().isDoor() && cellref.getLockLevel() <= 0 && ptr.getCellRef().getTrap().empty()) + return true; + + // TODO: implement a better check to check if item is owned bed + if (item.getClass().isActivator() && item.getClass().getScript(item).compare(0, 3, "Bed") != 0) + return true; + const std::string& owner = cellref.getOwner(); bool isOwned = !owner.empty() && owner != "player"; @@ -888,7 +908,7 @@ namespace MWMechanics } MWWorld::Ptr victim; - if (isAllowedToUse(ptr, bed.getCellRef(), victim)) + if (isAllowedToUse(ptr, bed, victim)) return false; if(commitCrime(ptr, victim, OT_SleepingInOwnedBed)) @@ -903,7 +923,7 @@ namespace MWMechanics void MechanicsManager::objectOpened(const MWWorld::Ptr &ptr, const MWWorld::Ptr &item) { MWWorld::Ptr victim; - if (isAllowedToUse(ptr, item.getCellRef(), victim)) + if (isAllowedToUse(ptr, item, victim)) return; commitCrime(ptr, victim, OT_Trespassing); } @@ -933,6 +953,43 @@ namespace MWMechanics return ownerFound != owners.end(); } + void MechanicsManager::confiscateStolenItemToOwner(const MWWorld::Ptr &player, const MWWorld::Ptr &item, const MWWorld::Ptr& victim, int count) + { + if (player != getPlayer()) + return; + + const std::string itemId = Misc::StringUtils::lowerCase(item.getCellRef().getRefId()); + + StolenItemsMap::iterator stolenIt = mStolenItems.find(itemId); + if (stolenIt == mStolenItems.end()) + return; + + Owner owner; + owner.first = victim.getCellRef().getRefId(); + owner.second = false; + + Misc::StringUtils::lowerCaseInPlace(owner.first); + + // decrease count of stolen items + int toRemove = std::min(count, mStolenItems[itemId][owner]); + mStolenItems[itemId][owner] -= toRemove; + if (mStolenItems[itemId][owner] == 0) + { + // erase owner from stolen items owners + OwnerMap& owners = stolenIt->second; + OwnerMap::iterator ownersIt = owners.find(owner); + if (ownersIt != owners.end()) + owners.erase(ownersIt); + } + + MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); + + // move items from player to owner and report about theft + victim.getClass().getContainerStore(victim).add(item, toRemove, victim); + store.remove(item, toRemove, player); + commitCrime(player, victim, OT_Theft, item.getClass().getValue(item) * toRemove); + } + void MechanicsManager::confiscateStolenItems(const MWWorld::Ptr &player, const MWWorld::Ptr &targetContainer) { MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); @@ -986,7 +1043,7 @@ namespace MWMechanics } } - if (isAllowedToUse(ptr, *ownerCellRef, victim)) + if (isAllowedToUse(ptr, item, victim)) return; Owner owner; @@ -1067,11 +1124,6 @@ namespace MWMechanics End of tes3mp addition */ - if (type == OT_Theft || type == OT_Pickpocket) - MWBase::Environment::get().getDialogueManager()->say(*it, "thief"); - else if (type == OT_Trespassing) - MWBase::Environment::get().getDialogueManager()->say(*it, "intruder"); - crimeSeen = true; } } @@ -1173,10 +1225,25 @@ namespace MWMechanics if (it->getClass().getCreatureStats(*it).getAiSequence().isInCombat(victim)) continue; + // Player's followers should not attack player, or try to arrest him + if (it->getClass().getCreatureStats(*it).getAiSequence().hasPackage(AiPackage::TypeIdFollow)) + { + std::set playerFollowers; + getActorsSidingWith(player, playerFollowers); + + if (playerFollowers.find(*it) != playerFollowers.end()) + continue; + } + // Will the witness report the crime? if (it->getClass().getCreatureStats(*it).getAiSetting(CreatureStats::AI_Alarm).getBase() >= 100) { reported = true; + + if (type == OT_Theft || type == OT_Pickpocket) + MWBase::Environment::get().getDialogueManager()->say(*it, "thief"); + else if (type == OT_Trespassing) + MWBase::Environment::get().getDialogueManager()->say(*it, "intruder"); } if (it->getClass().isClass(*it, "guard")) @@ -1189,7 +1256,9 @@ namespace MWMechanics it->getClass().getNpcStats(*it).setCrimeId(id); if (!it->getClass().getCreatureStats(*it).getAiSequence().hasPackage(AiPackage::TypeIdPursue)) + { it->getClass().getCreatureStats(*it).getAiSequence().stack(AiPursue(player), *it); + } } else { @@ -1579,6 +1648,11 @@ namespace MWMechanics return mActors.isReadyToBlock(ptr); } + bool MechanicsManager::isAttackingOrSpell(const MWWorld::Ptr &ptr) const + { + return mActors.isAttackingOrSpell(ptr); + } + void MechanicsManager::setWerewolf(const MWWorld::Ptr& actor, bool werewolf) { MWMechanics::NpcStats& npcStats = actor.getClass().getNpcStats(actor); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 55481024c..d6b6d1275 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -198,6 +198,8 @@ namespace MWMechanics virtual void keepPlayerAlive(); virtual bool isReadyToBlock (const MWWorld::Ptr& ptr) const; + /// Is \a ptr casting spell or using weapon now? + virtual bool isAttackingOrSpell(const MWWorld::Ptr &ptr) const; virtual void confiscateStolenItems (const MWWorld::Ptr& player, const MWWorld::Ptr& targetContainer); @@ -207,15 +209,19 @@ namespace MWMechanics /// Has the player stolen this item from the given owner? virtual bool isItemStolenFrom(const std::string& itemid, const std::string& ownerid); - + /// @return is \a ptr allowed to take/use \a cellref or is it a crime? - virtual bool isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::CellRef& cellref, MWWorld::Ptr& victim); + virtual bool isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::ConstPtr& item, MWWorld::Ptr& victim); virtual void setWerewolf(const MWWorld::Ptr& actor, bool werewolf); virtual void applyWerewolfAcrobatics(const MWWorld::Ptr& actor); virtual void cleanupSummonedCreature(const MWWorld::Ptr& caster, int creatureActorId); + virtual void confiscateStolenItemToOwner(const MWWorld::Ptr &player, const MWWorld::Ptr &item, const MWWorld::Ptr& victim, int count); + + virtual bool isRunning(const MWWorld::Ptr& ptr); + virtual bool isSneaking(const MWWorld::Ptr& ptr); private: void reportCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim, OffenseType type, int arg=0); diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index d93290302..91262753b 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -137,6 +137,11 @@ namespace MWMechanics CreatureStats& stats = actor.getClass().getCreatureStats(actor); + float castBonus = -stats.getMagicEffects().get(ESM::MagicEffect::Sound).getMagnitude(); + + float castChance = calcSpellBaseSuccessChance(spell, actor, effectiveSchool) + castBonus; + castChance *= stats.getFatigueTerm(); + if (stats.getMagicEffects().get(ESM::MagicEffect::Silence).getMagnitude()&& !godmode) return 0; @@ -154,11 +159,6 @@ namespace MWMechanics return 100; } - float castBonus = -stats.getMagicEffects().get(ESM::MagicEffect::Sound).getMagnitude(); - - float castChance = calcSpellBaseSuccessChance(spell, actor, effectiveSchool) + castBonus; - castChance *= stats.getFatigueTerm(); - if (!cap) return std::max(0.f, castChance); else @@ -715,7 +715,7 @@ namespace MWMechanics } else if (target.getClass().isActor() && effectId == ESM::MagicEffect::Dispel) { - target.getClass().getCreatureStats(target).getActiveSpells().purgeAll(magnitude); + target.getClass().getCreatureStats(target).getActiveSpells().purgeAll(magnitude, true); return true; } else if (target.getClass().isActor() && target == getPlayer()) diff --git a/apps/openmw/mwmechanics/spellpriority.cpp b/apps/openmw/mwmechanics/spellpriority.cpp index a73e4fd89..3a730fbeb 100644 --- a/apps/openmw/mwmechanics/spellpriority.cpp +++ b/apps/openmw/mwmechanics/spellpriority.cpp @@ -25,6 +25,14 @@ namespace const MWMechanics::ActiveSpells& activeSpells = actor.getClass().getCreatureStats(actor).getActiveSpells(); for (MWMechanics::ActiveSpells::TIterator it = activeSpells.begin(); it != activeSpells.end(); ++it) { + // if the effect filter is not specified, take in account only spells effects. Leave potions, enchanted items etc. + if (effectFilter == -1) + { + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(it->first); + if (!spell || spell->mData.mType != ESM::Spell::ST_Spell) + continue; + } + const MWMechanics::ActiveSpells::ActiveSpellParams& params = it->second; for (std::vector::const_iterator effectIt = params.mEffects.begin(); effectIt != params.mEffects.end(); ++effectIt) @@ -46,6 +54,26 @@ namespace } return toCure; } + + float getSpellDuration (const MWWorld::Ptr& actor, const std::string& spellId) + { + float duration = 0; + const MWMechanics::ActiveSpells& activeSpells = actor.getClass().getCreatureStats(actor).getActiveSpells(); + for (MWMechanics::ActiveSpells::TIterator it = activeSpells.begin(); it != activeSpells.end(); ++it) + { + if (it->first != spellId) + continue; + + const MWMechanics::ActiveSpells::ActiveSpellParams& params = it->second; + for (std::vector::const_iterator effectIt = params.mEffects.begin(); + effectIt != params.mEffects.end(); ++effectIt) + { + if (effectIt->mDuration > duration) + duration = effectIt->mDuration; + } + } + return duration; + } } namespace MWMechanics @@ -114,15 +142,39 @@ namespace MWMechanics const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find(ptr.getClass().getEnchantment(ptr)); + // Spells don't stack, so early out if the spell is still active on the target + int types = getRangeTypes(enchantment->mEffects); + if ((types & Self) && actor.getClass().getCreatureStats(actor).getActiveSpells().isSpellActive(ptr.getCellRef().getRefId())) + return 0.f; + + if (types & (Touch|Target) && getSpellDuration(enemy, ptr.getCellRef().getRefId()) > 3) + return 0.f; + if (enchantment->mData.mType == ESM::Enchantment::CastOnce) { return rateEffects(enchantment->mEffects, actor, enemy); } - else + else if (enchantment->mData.mType == ESM::Enchantment::WhenUsed) { - //if (!ptr.getClass().canBeEquipped(ptr, actor)) - return 0.f; + MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); + + // Creatures can not wear armor/clothing, so allow creatures to use non-equipped items, + if (actor.getClass().isNpc() && !store.isEquipped(ptr)) + return 0.f; + + int castCost = getEffectiveEnchantmentCastCost(static_cast(enchantment->mData.mCost), actor); + + if (ptr.getCellRef().getEnchantmentCharge() != -1 + && ptr.getCellRef().getEnchantmentCharge() < castCost) + return 0.f; + + float rating = rateEffects(enchantment->mEffects, actor, enemy); + + rating *= 2; // prefer rechargable magic items over spells + return rating; } + + return 0.f; } float rateEffect(const ESM::ENAMstruct &effect, const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy) @@ -444,6 +496,15 @@ namespace MWMechanics break; } + // Allow only one summoned creature at time + if (isSummoningEffect(effect.mEffectID)) + { + MWMechanics::CreatureStats& creatureStats = actor.getClass().getCreatureStats(actor); + + if (!creatureStats.getSummonedCreatureMap().empty()) + return 0.f; + } + const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effect.mEffectID); // Underwater casting not possible diff --git a/apps/openmw/mwmechanics/summoning.cpp b/apps/openmw/mwmechanics/summoning.cpp index fd3004406..14890f83f 100644 --- a/apps/openmw/mwmechanics/summoning.cpp +++ b/apps/openmw/mwmechanics/summoning.cpp @@ -52,26 +52,10 @@ namespace MWMechanics } } - void UpdateSummonedCreatures::process() + void UpdateSummonedCreatures::process(bool cleanup) { - - MWMechanics::CreatureStats& creatureStats = mActor.getClass().getCreatureStats(mActor); - - // Update summon effects std::map& creatureMap = creatureStats.getSummonedCreatureMap(); - for (std::map::iterator it = creatureMap.begin(); it != creatureMap.end(); ) - { - bool found = mActiveEffects.find(it->first) != mActiveEffects.end(); - if (!found) - { - // Effect has ended - MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(mActor, it->second); - creatureMap.erase(it++); - continue; - } - ++it; - } for (std::set >::iterator it = mActiveEffects.begin(); it != mActiveEffects.end(); ++it) { @@ -143,21 +127,18 @@ namespace MWMechanics } } + // Update summon effects for (std::map::iterator it = creatureMap.begin(); it != creatureMap.end(); ) { - MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->second); - if (!ptr.isEmpty() && ptr.getClass().getCreatureStats(ptr).isDead() && ptr.getClass().getCreatureStats(ptr).isDeathAnimationFinished()) + bool found = mActiveEffects.find(it->first) != mActiveEffects.end(); + if (!found) { - // Purge the magic effect so a new creature can be summoned if desired - creatureStats.getActiveSpells().purgeEffect(it->first.first, it->first.second); - if (mActor.getClass().hasInventoryStore(ptr)) - mActor.getClass().getInventoryStore(mActor).purgeEffect(it->first.first, it->first.second); - + // Effect has ended MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(mActor, it->second); creatureMap.erase(it++); + continue; } - else - ++it; + ++it; } std::vector& graveyard = creatureStats.getSummonedCreatureGraveyard(); @@ -192,6 +173,26 @@ namespace MWMechanics else ++it; } + + if (!cleanup) + return; + + for (std::map::iterator it = creatureMap.begin(); it != creatureMap.end(); ) + { + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->second); + if (ptr.isEmpty() || (ptr.getClass().getCreatureStats(ptr).isDead() && ptr.getClass().getCreatureStats(ptr).isDeathAnimationFinished())) + { + // Purge the magic effect so a new creature can be summoned if desired + creatureStats.getActiveSpells().purgeEffect(it->first.first, it->first.second); + if (mActor.getClass().hasInventoryStore(mActor)) + mActor.getClass().getInventoryStore(mActor).purgeEffect(it->first.first, it->first.second); + + MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(mActor, it->second); + creatureMap.erase(it++); + } + else + ++it; + } } } diff --git a/apps/openmw/mwmechanics/summoning.hpp b/apps/openmw/mwmechanics/summoning.hpp index b2a3c60ea..9329dcb83 100644 --- a/apps/openmw/mwmechanics/summoning.hpp +++ b/apps/openmw/mwmechanics/summoning.hpp @@ -22,7 +22,7 @@ namespace MWMechanics float magnitude, float remainingTime = -1, float totalTime = -1); /// To call after all effect sources have been visited - void process(); + void process(bool cleanup); private: MWWorld::Ptr mActor; diff --git a/apps/openmw/mwmechanics/weaponpriority.cpp b/apps/openmw/mwmechanics/weaponpriority.cpp index 07cf6ff5f..d06e73c93 100644 --- a/apps/openmw/mwmechanics/weaponpriority.cpp +++ b/apps/openmw/mwmechanics/weaponpriority.cpp @@ -13,6 +13,7 @@ #include "combat.hpp" #include "aicombataction.hpp" #include "spellpriority.hpp" +#include "spellcasting.hpp" namespace MWMechanics { @@ -90,10 +91,13 @@ namespace MWMechanics if (!weapon->mEnchant.empty()) { const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find(weapon->mEnchant); - if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes - && (item.getCellRef().getEnchantmentCharge() == -1 - || item.getCellRef().getEnchantmentCharge() >= enchantment->mData.mCost)) - rating += rateEffects(enchantment->mEffects, actor, enemy); + if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes) + { + int castCost = getEffectiveEnchantmentCastCost(static_cast(enchantment->mData.mCost), actor); + + if (item.getCellRef().getEnchantmentCharge() == -1 || item.getCellRef().getEnchantmentCharge() >= castCost) + rating += rateEffects(enchantment->mEffects, actor, enemy); + } } int skill = item.getClass().getEquipmentSkill(item); diff --git a/apps/openmw/mwscript/controlextensions.cpp b/apps/openmw/mwscript/controlextensions.cpp index 626fafb5a..21b3b5587 100644 --- a/apps/openmw/mwscript/controlextensions.cpp +++ b/apps/openmw/mwscript/controlextensions.cpp @@ -9,12 +9,14 @@ #include "../mwbase/environment.hpp" #include "../mwbase/inputmanager.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/world.hpp" #include "../mwworld/class.hpp" #include "../mwworld/ptr.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/movement.hpp" #include "interpretercontext.hpp" #include "ref.hpp" @@ -167,7 +169,11 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { MWWorld::Ptr ptr = MWBase::Environment::get().getWorld ()->getPlayerPtr(); - runtime.push (ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run)); + const MWWorld::Class &cls = ptr.getClass(); + + bool isRunning = MWBase::Environment::get().getMechanicsManager()->isRunning(ptr); + + runtime.push (isRunning && cls.getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run)); } }; @@ -177,8 +183,12 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { - MWWorld::Ptr ptr = MWBase::Environment::get().getWorld ()->getPlayerPtr(); - runtime.push (ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Sneak)); + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayerPtr(); + const MWWorld::Class &cls = ptr.getClass(); + + bool isSneaking = MWBase::Environment::get().getMechanicsManager()->isSneaking(ptr); + + runtime.push (isSneaking && cls.getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Sneak)); } }; diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 53c0643f6..811797369 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include @@ -34,7 +35,7 @@ namespace MWSound { - SoundManager::SoundManager(const VFS::Manager* vfs, const std::map& fallbackMap, bool useSound) + SoundManager::SoundManager(const VFS::Manager* vfs, const std::map& fallbackMap, bool useSound) : mVFS(vfs) , mFallback(fallbackMap) , mOutput(new DEFAULT_OUTPUT(*this)) @@ -271,7 +272,6 @@ namespace MWSound return sound; } - // Gets the combined volume settings for the given sound type float SoundManager::volumeFromType(PlayType type) const { @@ -298,7 +298,6 @@ namespace MWSound return volume; } - void SoundManager::stopMusic() { if(mMusic) @@ -328,14 +327,28 @@ namespace MWSound } } + void SoundManager::advanceMusic(const std::string& filename) + { + if (!isMusicPlaying()) + { + streamMusicFull(filename); + return; + } + + mNextMusic = filename; + + mMusic->setFadeout(0.5f); + } + void SoundManager::streamMusic(const std::string& filename) { - streamMusicFull("Music/"+filename); + advanceMusic("Music/"+filename); } void SoundManager::startRandomTitle() { std::vector filelist; + auto &tracklist = mMusicToPlay[mCurrentPlaylist]; if (mMusicFiles.find(mCurrentPlaylist) == mMusicFiles.end()) { const std::map& index = mVFS->getIndex(); @@ -354,7 +367,6 @@ namespace MWSound } mMusicFiles[mCurrentPlaylist] = filelist; - } else filelist = mMusicFiles[mCurrentPlaylist]; @@ -362,15 +374,25 @@ namespace MWSound if(filelist.empty()) return; - int i = Misc::Rng::rollDice(filelist.size()); + // Do a Fisher-Yates shuffle - // Don't play the same music track twice in a row - if (filelist[i] == mLastPlayedMusic) + // Repopulate if playlist is empty + if(tracklist.empty()) { - i = (i+1) % filelist.size(); + tracklist.resize(filelist.size()); + std::iota(tracklist.begin(), tracklist.end(), 0); } - streamMusicFull(filelist[i]); + int i = Misc::Rng::rollDice(tracklist.size()); + + // Reshuffle if last played music is the same after a repopulation + if(filelist[tracklist[i]] == mLastPlayedMusic) + i = (i+1) % tracklist.size(); + + // Remove music from list after advancing music + advanceMusic(filelist[tracklist[i]]); + tracklist[i] = tracklist.back(); + tracklist.pop_back(); } bool SoundManager::isMusicPlaying() @@ -925,6 +947,8 @@ namespace MWSound env ); + updateMusic(duration); + // Check if any sounds are finished playing, and trash them SoundMap::iterator snditer = mActiveSounds.begin(); while(snditer != mActiveSounds.end()) @@ -1029,6 +1053,23 @@ namespace MWSound } + void SoundManager::updateMusic(float duration) + { + if (!mNextMusic.empty()) + { + mMusic->updateFade(duration); + + mOutput->updateStream(mMusic); + + if (mMusic->getRealVolume() <= 0.f) + { + streamMusicFull(mNextMusic); + mNextMusic.clear(); + } + } + } + + void SoundManager::update(float duration) { if(!mOutput->isInitialized()) diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index bf628b102..691e52932 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include @@ -49,6 +50,7 @@ namespace MWSound // Caches available music tracks by std::map > mMusicFiles; + std::unordered_map> mMusicToPlay; // A list with music files not yet played std::string mLastPlayedMusic; // The music file that was last played float mMasterVolume; @@ -114,9 +116,14 @@ namespace MWSound MWBase::SoundStreamPtr playVoice(DecoderPtr decoder, const osg::Vec3f &pos, bool playlocal); void streamMusicFull(const std::string& filename); + void advanceMusic(const std::string& filename); + void updateSounds(float duration); void updateRegionSound(float duration); void updateWaterSound(float duration); + void updateMusic(float duration); + + std::string mNextMusic; float volumeFromType(PlayType type) const; diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 42d57ffab..097ec0faa 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -301,6 +301,10 @@ namespace MWWorld virtual Ptr copyToCell(const ConstPtr &ptr, CellStore &cell, const ESM::Position &pos, int count) const; + virtual bool isActivator() const { + return false; + } + virtual bool isActor() const { return false; } @@ -309,6 +313,10 @@ namespace MWWorld return false; } + virtual bool isDoor() const { + return false; + } + virtual bool isBipedal(const MWWorld::ConstPtr& ptr) const; virtual bool canFly(const MWWorld::ConstPtr& ptr) const; virtual bool canSwim(const MWWorld::ConstPtr& ptr) const; diff --git a/apps/openmw/mwworld/contentloader.hpp b/apps/openmw/mwworld/contentloader.hpp index 46bd7d3f9..0f2d807aa 100644 --- a/apps/openmw/mwworld/contentloader.hpp +++ b/apps/openmw/mwworld/contentloader.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "components/loadinglistener/loadinglistener.hpp" @@ -24,7 +25,7 @@ struct ContentLoader virtual void load(const boost::filesystem::path& filepath, int& index) { std::cout << "Loading content file " << filepath.string() << std::endl; - mListener.setLabel(filepath.string()); + mListener.setLabel(MyGUI::TextIterator::toTagsString(filepath.string())); } protected: diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index a4e44a263..a086c5619 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2743,7 +2743,25 @@ namespace MWWorld { pos.rot[0] = pos.rot[1] = pos.rot[2] = 0; - if (const ESM::Cell *ext = getExterior(name)) { + const ESM::Cell *ext = getExterior(name); + + if (!ext && name.find(',') != std::string::npos) { + try { + int x = std::stoi(name.substr(0, name.find(','))); + int y = std::stoi(name.substr(name.find(',')+1)); + ext = getExterior(x, y)->getCell(); + } + catch (std::invalid_argument) + { + // This exception can be ignored, as this means that name probably refers to a interior cell instead of comma separated coordinates + } + catch (std::out_of_range) + { + throw std::runtime_error("Cell coordinates out of range."); + } + } + + if (ext) { int x = ext->getGridX(); int y = ext->getGridY(); indexToPosition(x, y, pos.pos[0], pos.pos[1], true); @@ -2753,6 +2771,7 @@ namespace MWWorld return true; } + return false; } diff --git a/apps/wizard/componentselectionpage.cpp b/apps/wizard/componentselectionpage.cpp index 161e51515..d99884966 100644 --- a/apps/wizard/componentselectionpage.cpp +++ b/apps/wizard/componentselectionpage.cpp @@ -2,7 +2,6 @@ #include #include -#include #include #include "mainwizard.hpp" @@ -26,7 +25,7 @@ Wizard::ComponentSelectionPage::ComponentSelectionPage(QWidget *parent) : void Wizard::ComponentSelectionPage::updateButton(QListWidgetItem*) { - if (field(QLatin1String("installation.new")).toBool() == true) + if (field(QLatin1String("installation.retailDisc")).toBool() == true) return; // Morrowind is always checked here bool unchecked = true; @@ -60,7 +59,7 @@ void Wizard::ComponentSelectionPage::initializePage() QListWidgetItem *tribunalItem = new QListWidgetItem(QLatin1String("Tribunal")); QListWidgetItem *bloodmoonItem = new QListWidgetItem(QLatin1String("Bloodmoon")); - if (field(QLatin1String("installation.new")).toBool() == true) + if (field(QLatin1String("installation.retailDisc")).toBool() == true) { morrowindItem->setFlags((morrowindItem->flags() & ~Qt::ItemIsEnabled) | Qt::ItemIsUserCheckable); morrowindItem->setData(Qt::CheckStateRole, Qt::Checked); @@ -117,7 +116,7 @@ bool Wizard::ComponentSelectionPage::validatePage() // qDebug() << components << path << mWizard->mInstallations[path]; - if (field(QLatin1String("installation.new")).toBool() == false) { + if (field(QLatin1String("installation.retailDisc")).toBool() == false) { if (components.contains(QLatin1String("Tribunal")) && !components.contains(QLatin1String("Bloodmoon"))) { if (mWizard->mInstallations[path].hasBloodmoon) diff --git a/apps/wizard/conclusionpage.cpp b/apps/wizard/conclusionpage.cpp index 87154732a..f6a6015b8 100644 --- a/apps/wizard/conclusionpage.cpp +++ b/apps/wizard/conclusionpage.cpp @@ -16,14 +16,14 @@ Wizard::ConclusionPage::ConclusionPage(QWidget *parent) : void Wizard::ConclusionPage::initializePage() { // Write the path to openmw.cfg - if (field(QLatin1String("installation.new")).toBool() == true) { + if (field(QLatin1String("installation.retailDisc")).toBool() == true) { QString path(field(QLatin1String("installation.path")).toString()); mWizard->addInstallation(path); } if (!mWizard->mError) { - if ((field(QLatin1String("installation.new")).toBool() == true) + if ((field(QLatin1String("installation.retailDisc")).toBool() == true) || (field(QLatin1String("installation.import-settings")).toBool() == true)) { qDebug() << "IMPORT SETTINGS"; @@ -33,7 +33,7 @@ void Wizard::ConclusionPage::initializePage() if (!mWizard->mError) { - if (field(QLatin1String("installation.new")).toBool() == true) + if (field(QLatin1String("installation.retailDisc")).toBool() == true) { textLabel->setText(tr("

The OpenMW Wizard successfully installed Morrowind on your computer.

\

Click Finish to close the Wizard.

")); diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp index 2f0af88c9..7a4dcbf10 100644 --- a/apps/wizard/installationpage.cpp +++ b/apps/wizard/installationpage.cpp @@ -7,7 +7,6 @@ #include #include "mainwizard.hpp" -#include "inisettings.hpp" Wizard::InstallationPage::InstallationPage(QWidget *parent) : QWizardPage(parent) @@ -76,7 +75,7 @@ void Wizard::InstallationPage::initializePage() // That way installing all three components would yield 300% // When one component is done the bar will be filled by 33% - if (field(QLatin1String("installation.new")).toBool() == true) { + if (field(QLatin1String("installation.retailDisc")).toBool() == true) { installProgressBar->setMaximum((components.count() * 100)); } else { if (components.contains(QLatin1String("Tribunal")) @@ -96,7 +95,7 @@ void Wizard::InstallationPage::startInstallation() QStringList components(field(QLatin1String("installation.components")).toStringList()); QString path(field(QLatin1String("installation.path")).toString()); - if (field(QLatin1String("installation.new")).toBool() == true) + if (field(QLatin1String("installation.retailDisc")).toBool() == true) { // Always install Morrowind mUnshield->setInstallComponent(Wizard::Component_Morrowind, true); @@ -227,7 +226,7 @@ bool Wizard::InstallationPage::isComplete() const int Wizard::InstallationPage::nextId() const { - if (field(QLatin1String("installation.new")).toBool() == true) { + if (field(QLatin1String("installation.retailDisc")).toBool() == true) { return MainWizard::Page_Conclusion; } else { if (!mWizard->mError) { diff --git a/apps/wizard/languageselectionpage.cpp b/apps/wizard/languageselectionpage.cpp index 0d5132f5b..4c10bf38c 100644 --- a/apps/wizard/languageselectionpage.cpp +++ b/apps/wizard/languageselectionpage.cpp @@ -30,7 +30,7 @@ void Wizard::LanguageSelectionPage::initializePage() int Wizard::LanguageSelectionPage::nextId() const { - if (field(QLatin1String("installation.new")).toBool() == true) { + if (field(QLatin1String("installation.retailDisc")).toBool() == true) { return MainWizard::Page_ComponentSelection; } else { QString path(field(QLatin1String("installation.path")).toString()); diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index 7ef8761dd..b99f151aa 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -3,7 +3,6 @@ #include #include -#include #include #include #include @@ -258,7 +257,7 @@ void Wizard::MainWizard::runSettingsImporter() QStringList arguments; // Import plugin selection? - if (field(QLatin1String("installation.new")).toBool() == true + if (field(QLatin1String("installation.retailDisc")).toBool() == true || field(QLatin1String("installation.import-addons")).toBool() == true) arguments.append(QLatin1String("--game-files")); @@ -278,7 +277,7 @@ void Wizard::MainWizard::runSettingsImporter() // Now the paths arguments.append(QLatin1String("--ini")); - if (field(QLatin1String("installation.new")).toBool() == true) { + if (field(QLatin1String("installation.retailDisc")).toBool() == true) { arguments.append(path + QDir::separator() + QLatin1String("Morrowind.ini")); } else { arguments.append(mInstallations[path].iniPath); diff --git a/apps/wizard/methodselectionpage.cpp b/apps/wizard/methodselectionpage.cpp index 5f3917bd5..e00344af9 100644 --- a/apps/wizard/methodselectionpage.cpp +++ b/apps/wizard/methodselectionpage.cpp @@ -1,5 +1,4 @@ #include "methodselectionpage.hpp" -#include #include "mainwizard.hpp" Wizard::MethodSelectionPage::MethodSelectionPage(QWidget *parent) : @@ -10,16 +9,16 @@ Wizard::MethodSelectionPage::MethodSelectionPage(QWidget *parent) : setupUi(this); #ifndef OPENMW_USE_UNSHIELD - newLocationRadioButton->setEnabled(false); + retailDiscRadioButton->setEnabled(false); existingLocationRadioButton->setChecked(true); #endif - registerField(QLatin1String("installation.new"), newLocationRadioButton); + registerField(QLatin1String("installation.retailDisc"), retailDiscRadioButton); } int Wizard::MethodSelectionPage::nextId() const { - if (field(QLatin1String("installation.new")).toBool() == true) { + if (field(QLatin1String("installation.retailDisc")).toBool() == true) { return MainWizard::Page_InstallationTarget; } else { return MainWizard::Page_ExistingInstallation; diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index e514cca12..78186c439 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1674,6 +1674,8 @@ namespace NifOsg bool hasMatCtrl = false; + int lightmode = 1; + for (std::vector::const_reverse_iterator it = properties.rbegin(); it != properties.rend(); ++it) { const Nif::Property* property = *it; @@ -1706,19 +1708,22 @@ namespace NifOsg case Nif::RC_NiVertexColorProperty: { const Nif::NiVertexColorProperty* vertprop = static_cast(property); - if (!hasVertexColors) - break; - switch (vertprop->flags) + lightmode = vertprop->data.lightmode; + + switch (vertprop->data.vertmode) { - case 0: - mat->setColorMode(osg::Material::OFF); - break; - case 1: - mat->setColorMode(osg::Material::EMISSION); - break; - case 2: - mat->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); - break; + case 0: + mat->setColorMode(osg::Material::OFF); + break; + case 1: + mat->setColorMode(osg::Material::EMISSION); + break; + case 2: + if (lightmode != 0) + mat->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); + else + mat->setColorMode(osg::Material::OFF); + break; } break; } @@ -1772,6 +1777,35 @@ namespace NifOsg mat->setColorMode(osg::Material::AMBIENT); } + if (lightmode == 0) + { + osg::Vec4f diffuse = mat->getDiffuse(osg::Material::FRONT_AND_BACK); + diffuse = osg::Vec4f(0,0,0,diffuse.a()); + mat->setDiffuse(osg::Material::FRONT_AND_BACK, diffuse); + mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f()); + } + + // If we're told to use vertex colors but there are none to use, use a default color instead. + if (!hasVertexColors) + { + switch (mat->getColorMode()) + { + case osg::Material::AMBIENT: + mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); + break; + case osg::Material::AMBIENT_AND_DIFFUSE: + mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); + mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); + break; + case osg::Material::EMISSION: + mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); + break; + default: + break; + } + mat->setColorMode(osg::Material::OFF); + } + if (!hasMatCtrl && mat->getColorMode() == osg::Material::OFF && mat->getEmission(osg::Material::FRONT_AND_BACK) == osg::Vec4f(0,0,0,1) && mat->getDiffuse(osg::Material::FRONT_AND_BACK) == osg::Vec4f(1,1,1,1) diff --git a/components/resource/bulletshapemanager.cpp b/components/resource/bulletshapemanager.cpp index 010917572..c1a7eb8f3 100644 --- a/components/resource/bulletshapemanager.cpp +++ b/components/resource/bulletshapemanager.cpp @@ -190,6 +190,13 @@ void BulletShapeManager::updateCache(double referenceTime) mInstanceCache->removeUnreferencedObjectsInCache(); } +void BulletShapeManager::clearCache() +{ + ResourceManager::clearCache(); + + mInstanceCache->clear(); +} + void BulletShapeManager::reportStats(unsigned int frameNumber, osg::Stats *stats) const { stats->setAttribute(frameNumber, "Shape", mCache->getCacheSize()); diff --git a/components/resource/bulletshapemanager.hpp b/components/resource/bulletshapemanager.hpp index fec7251ac..8ae2b531f 100644 --- a/components/resource/bulletshapemanager.hpp +++ b/components/resource/bulletshapemanager.hpp @@ -42,6 +42,8 @@ namespace Resource /// @see ResourceManager::updateCache virtual void updateCache(double referenceTime); + virtual void clearCache(); + void reportStats(unsigned int frameNumber, osg::Stats *stats) const; private: diff --git a/components/resource/multiobjectcache.cpp b/components/resource/multiobjectcache.cpp index fcda455cc..266139f3c 100644 --- a/components/resource/multiobjectcache.cpp +++ b/components/resource/multiobjectcache.cpp @@ -43,6 +43,12 @@ namespace Resource objectsToRemove.clear(); } + void MultiObjectCache::clear() + { + OpenThreads::ScopedLock lock(_objectCacheMutex); + _objectCache.clear(); + } + void MultiObjectCache::addEntryToObjectCache(const std::string &filename, osg::Object *object) { OpenThreads::ScopedLock lock(_objectCacheMutex); diff --git a/components/resource/multiobjectcache.hpp b/components/resource/multiobjectcache.hpp index a314a9e4b..527247bf9 100644 --- a/components/resource/multiobjectcache.hpp +++ b/components/resource/multiobjectcache.hpp @@ -25,6 +25,9 @@ namespace Resource void removeUnreferencedObjectsInCache(); + /** Remove all objects from the cache. */ + void clear(); + void addEntryToObjectCache(const std::string& filename, osg::Object* object); /** Take an Object from cache. Return NULL if no object found. */ diff --git a/components/resource/resourcemanager.cpp b/components/resource/resourcemanager.cpp index d19d9cf80..c0e99674e 100644 --- a/components/resource/resourcemanager.cpp +++ b/components/resource/resourcemanager.cpp @@ -23,6 +23,11 @@ namespace Resource mCache->removeExpiredObjectsInCache(referenceTime - mExpiryDelay); } + void ResourceManager::clearCache() + { + mCache->clear(); + } + void ResourceManager::setExpiryDelay(double expiryDelay) { mExpiryDelay = expiryDelay; @@ -33,4 +38,9 @@ namespace Resource return mVFS; } + void ResourceManager::releaseGLObjects(osg::State *state) + { + mCache->releaseGLObjects(state); + } + } diff --git a/components/resource/resourcemanager.hpp b/components/resource/resourcemanager.hpp index 61599cd5e..6031ecc01 100644 --- a/components/resource/resourcemanager.hpp +++ b/components/resource/resourcemanager.hpp @@ -11,6 +11,7 @@ namespace VFS namespace osg { class Stats; + class State; } namespace Resource @@ -28,6 +29,9 @@ namespace Resource /// Clear cache entries that have not been referenced for longer than expiryDelay. virtual void updateCache(double referenceTime); + /// Clear all cache entries. + virtual void clearCache(); + /// How long to keep objects in cache after no longer being referenced. void setExpiryDelay (double expiryDelay); @@ -35,6 +39,8 @@ namespace Resource virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) const {} + virtual void releaseGLObjects(osg::State* state); + protected: const VFS::Manager* mVFS; osg::ref_ptr mCache; diff --git a/components/resource/resourcesystem.cpp b/components/resource/resourcesystem.cpp index 8d05a1b4e..4d61dce69 100644 --- a/components/resource/resourcesystem.cpp +++ b/components/resource/resourcesystem.cpp @@ -68,6 +68,12 @@ namespace Resource (*it)->updateCache(referenceTime); } + void ResourceSystem::clearCache() + { + for (std::vector::iterator it = mResourceManagers.begin(); it != mResourceManagers.end(); ++it) + (*it)->clearCache(); + } + void ResourceSystem::addResourceManager(ResourceManager *resourceMgr) { mResourceManagers.push_back(resourceMgr); @@ -91,4 +97,10 @@ namespace Resource (*it)->reportStats(frameNumber, stats); } + void ResourceSystem::releaseGLObjects(osg::State *state) + { + for (std::vector::const_iterator it = mResourceManagers.begin(); it != mResourceManagers.end(); ++it) + (*it)->releaseGLObjects(state); + } + } diff --git a/components/resource/resourcesystem.hpp b/components/resource/resourcesystem.hpp index dc608b875..396bdb8fa 100644 --- a/components/resource/resourcesystem.hpp +++ b/components/resource/resourcesystem.hpp @@ -12,6 +12,7 @@ namespace VFS namespace osg { class Stats; + class State; } namespace Resource @@ -41,6 +42,10 @@ namespace Resource /// @note May be called from any thread if you do not add or remove resource managers at that point. void updateCache(double referenceTime); + /// Indicates to each resource manager to clear the entire cache. + /// @note May be called from any thread if you do not add or remove resource managers at that point. + void clearCache(); + /// Add this ResourceManager to be handled by the ResourceSystem. /// @note Does not transfer ownership. void addResourceManager(ResourceManager* resourceMgr); @@ -56,6 +61,9 @@ namespace Resource void reportStats(unsigned int frameNumber, osg::Stats* stats) const; + /// Call releaseGLObjects for each resource manager. + void releaseGLObjects(osg::State* state); + private: std::unique_ptr mSceneManager; std::unique_ptr mImageManager; diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 226933760..ab801ab82 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -122,6 +122,13 @@ namespace Resource { return _sharedStateSetList.size(); } + + void clearCache() + { + OpenThreads::ScopedLock lock(_listMutex); + _sharedTextureList.clear(); + _sharedStateSetList.clear(); + } }; /// Set texture filtering settings on textures contained in a FlipController. @@ -621,6 +628,11 @@ namespace Resource { mCache->releaseGLObjects(state); mInstanceCache->releaseGLObjects(state); + + mShaderManager->releaseGLObjects(state); + + OpenThreads::ScopedLock lock(mSharedStateMutex); + mSharedStateManager->releaseGLObjects(state); } void SceneManager::setIncrementalCompileOperation(osgUtil::IncrementalCompileOperation *ico) @@ -710,6 +722,15 @@ namespace Resource mSharedStateMutex.unlock(); } + void SceneManager::clearCache() + { + ResourceManager::clearCache(); + + OpenThreads::ScopedLock lock(mSharedStateMutex); + mSharedStateManager->clearCache(); + mInstanceCache->clear(); + } + void SceneManager::reportStats(unsigned int frameNumber, osg::Stats *stats) const { { diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index c6ff02acf..65524f76e 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -116,7 +116,7 @@ namespace Resource /// Manually release created OpenGL objects for the given graphics context. This may be required /// in cases where multiple contexts are used over the lifetime of the application. - void releaseGLObjects(osg::State* state); + void releaseGLObjects(osg::State* state) override; /// Set up an IncrementalCompileOperation for background compiling of loaded scenes. void setIncrementalCompileOperation(osgUtil::IncrementalCompileOperation* ico); @@ -143,6 +143,8 @@ namespace Resource /// @see ResourceManager::updateCache virtual void updateCache(double referenceTime); + virtual void clearCache(); + virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) const; private: diff --git a/components/sdlutil/sdlinputwrapper.cpp b/components/sdlutil/sdlinputwrapper.cpp index 56411ca88..a76de00d1 100644 --- a/components/sdlutil/sdlinputwrapper.cpp +++ b/components/sdlutil/sdlinputwrapper.cpp @@ -1,7 +1,6 @@ #include "sdlinputwrapper.hpp" #include -#include #include @@ -49,42 +48,17 @@ InputWrapper::InputWrapper(SDL_Window* window, osg::ref_ptr v SDL_PumpEvents(); - SDL_Event event; + SDL_Event evt; if (windowEventsOnly) { // During loading, just handle window events, and keep others for later - while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_WINDOWEVENT, SDL_WINDOWEVENT)) - handleWindowEvent(event); + while (SDL_PeepEvents(&evt, 1, SDL_GETEVENT, SDL_WINDOWEVENT, SDL_WINDOWEVENT)) + handleWindowEvent(evt); return; } - // Merge redundant events to avoid unnecessary listener calls - std::vector events; - while(SDL_PollEvent(&event)) { - if (events.empty() || events.back().type != event.type) - { - events.emplace_back(event); - continue; - } - - SDL_Event& previousEvent = events.back(); - - switch (event.type) - { - case SDL_MOUSEMOTION: - previousEvent.motion.x = event.motion.x; - previousEvent.motion.y = event.motion.y; - previousEvent.motion.xrel += event.motion.xrel; - previousEvent.motion.yrel += event.motion.yrel; - break; - default: - events.emplace_back(event); - } - } - - - for (const SDL_Event& evt : events) + while(SDL_PollEvent(&evt)) { switch(evt.type) { @@ -448,7 +422,6 @@ InputWrapper::InputWrapper(SDL_Window* window, osg::ref_ptr v mKeyMap.insert( KeyMap::value_type(SDLK_o, OIS::KC_O) ); mKeyMap.insert( KeyMap::value_type(SDLK_p, OIS::KC_P) ); mKeyMap.insert( KeyMap::value_type(SDLK_RETURN, OIS::KC_RETURN) ); - mKeyMap.insert( KeyMap::value_type(SDLK_LCTRL, OIS::KC_LCONTROL)); mKeyMap.insert( KeyMap::value_type(SDLK_a, OIS::KC_A) ); mKeyMap.insert( KeyMap::value_type(SDLK_s, OIS::KC_S) ); mKeyMap.insert( KeyMap::value_type(SDLK_d, OIS::KC_D) ); @@ -524,9 +497,20 @@ InputWrapper::InputWrapper(SDL_Window* window, osg::ref_ptr v mKeyMap.insert( KeyMap::value_type(SDLK_INSERT, OIS::KC_INSERT) ); mKeyMap.insert( KeyMap::value_type(SDLK_DELETE, OIS::KC_DELETE) ); mKeyMap.insert( KeyMap::value_type(SDLK_KP_ENTER, OIS::KC_NUMPADENTER) ); - mKeyMap.insert( KeyMap::value_type(SDLK_RCTRL, OIS::KC_RCONTROL) ); + mKeyMap.insert( KeyMap::value_type(SDLK_APPLICATION, OIS::KC_APPS) ); + +//The function of the Ctrl and Meta keys are switched on macOS compared to other platforms. +//For instance, Cmd+C versus Ctrl+C to copy from the system clipboard +#if defined(__APPLE__) + mKeyMap.insert( KeyMap::value_type(SDLK_LGUI, OIS::KC_LCONTROL) ); + mKeyMap.insert( KeyMap::value_type(SDLK_RGUI, OIS::KC_RCONTROL) ); + mKeyMap.insert( KeyMap::value_type(SDLK_LCTRL, OIS::KC_LWIN)); + mKeyMap.insert( KeyMap::value_type(SDLK_RCTRL, OIS::KC_RWIN) ); +#else mKeyMap.insert( KeyMap::value_type(SDLK_LGUI, OIS::KC_LWIN) ); mKeyMap.insert( KeyMap::value_type(SDLK_RGUI, OIS::KC_RWIN) ); - mKeyMap.insert( KeyMap::value_type(SDLK_APPLICATION, OIS::KC_APPS) ); + mKeyMap.insert( KeyMap::value_type(SDLK_LCTRL, OIS::KC_LCONTROL)); + mKeyMap.insert( KeyMap::value_type(SDLK_RCTRL, OIS::KC_RCONTROL) ); +#endif } } diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp index 2bfb17b5c..7cb49c6cb 100644 --- a/components/shader/shadermanager.cpp +++ b/components/shader/shadermanager.cpp @@ -158,4 +158,13 @@ namespace Shader return found->second; } + void ShaderManager::releaseGLObjects(osg::State *state) + { + OpenThreads::ScopedLock lock(mMutex); + for (auto shader : mShaders) + shader.second->releaseGLObjects(state); + for (auto program : mPrograms) + program.second->releaseGLObjects(state); + } + } diff --git a/components/shader/shadermanager.hpp b/components/shader/shadermanager.hpp index 5196dbe80..bd820a725 100644 --- a/components/shader/shadermanager.hpp +++ b/components/shader/shadermanager.hpp @@ -32,6 +32,7 @@ namespace Shader osg::ref_ptr getProgram(osg::ref_ptr vertexShader, osg::ref_ptr fragmentShader); + void releaseGLObjects(osg::State* state); private: std::string mPath; diff --git a/components/terrain/buffercache.cpp b/components/terrain/buffercache.cpp index 470655539..1734686de 100644 --- a/components/terrain/buffercache.cpp +++ b/components/terrain/buffercache.cpp @@ -231,4 +231,30 @@ namespace Terrain return buffer; } + void BufferCache::clearCache() + { + { + OpenThreads::ScopedLock lock(mIndexBufferMutex); + mIndexBufferMap.clear(); + } + { + OpenThreads::ScopedLock lock(mUvBufferMutex); + mUvBufferMap.clear(); + } + } + + void BufferCache::releaseGLObjects(osg::State *state) + { + { + OpenThreads::ScopedLock lock(mIndexBufferMutex); + for (auto indexbuffer : mIndexBufferMap) + indexbuffer.second->releaseGLObjects(state); + } + { + OpenThreads::ScopedLock lock(mUvBufferMutex); + for (auto uvbuffer : mUvBufferMap) + uvbuffer.second->releaseGLObjects(state); + } + } + } diff --git a/components/terrain/buffercache.hpp b/components/terrain/buffercache.hpp index e8963354b..37563d2c6 100644 --- a/components/terrain/buffercache.hpp +++ b/components/terrain/buffercache.hpp @@ -22,7 +22,9 @@ namespace Terrain /// @note Thread safe. osg::ref_ptr getUVBuffer(unsigned int numVerts); - // TODO: add releaseGLObjects() for our vertex/element buffer objects + void clearCache(); + + void releaseGLObjects(osg::State* state); private: // Index buffers are shared across terrain batches where possible. There is one index buffer for each diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index e4a7c2d68..7575113ef 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -55,6 +55,19 @@ void ChunkManager::reportStats(unsigned int frameNumber, osg::Stats *stats) cons stats->setAttribute(frameNumber, "Terrain Chunk", mCache->getCacheSize()); } +void ChunkManager::clearCache() +{ + ResourceManager::clearCache(); + + mBufferCache.clearCache(); +} + +void ChunkManager::releaseGLObjects(osg::State *state) +{ + ResourceManager::releaseGLObjects(state); + mBufferCache.releaseGLObjects(state); +} + void ChunkManager::setCullingActive(bool active) { mCullingActive = active; diff --git a/components/terrain/chunkmanager.hpp b/components/terrain/chunkmanager.hpp index 553e06d97..46531f23e 100644 --- a/components/terrain/chunkmanager.hpp +++ b/components/terrain/chunkmanager.hpp @@ -34,6 +34,10 @@ namespace Terrain virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) const; + virtual void clearCache(); + + void releaseGLObjects(osg::State* state) override; + void setCullingActive(bool active); private: diff --git a/components/vfs/manager.cpp b/components/vfs/manager.cpp index 457947d40..4f3994bac 100644 --- a/components/vfs/manager.cpp +++ b/components/vfs/manager.cpp @@ -39,6 +39,12 @@ namespace VFS Manager::~Manager() { + reset(); + } + + void Manager::reset() + { + mIndex.clear(); for (std::vector::iterator it = mArchives.begin(); it != mArchives.end(); ++it) delete *it; mArchives.clear(); diff --git a/components/vfs/manager.hpp b/components/vfs/manager.hpp index 6592a65a8..c5f0a8fec 100644 --- a/components/vfs/manager.hpp +++ b/components/vfs/manager.hpp @@ -26,6 +26,9 @@ namespace VFS ~Manager(); + // Empty the file index and unregister archives. + void reset(); + /// Register the given archive. All files contained in it will be added to the index on the next buildIndex() call. /// @note Takes ownership of the given pointer. void addArchive(Archive* archive); diff --git a/docs/source/manuals/openmw-cs/files-and-directories.rst b/docs/source/manuals/openmw-cs/files-and-directories.rst index 34680fa94..77593dece 100644 --- a/docs/source/manuals/openmw-cs/files-and-directories.rst +++ b/docs/source/manuals/openmw-cs/files-and-directories.rst @@ -14,22 +14,22 @@ Basics Directories =========== -OpenMW and OpenMW CS us multiple directories on the file system. First of all +OpenMW and OpenMW CS use multiple directories on the file system. First of all there is a *user directory* that holds configuration files and a number of different sub-directories. The location of the user directory is hard-coded into the CS and depends on your operating system. ================ ========================================= -Operating System User Dircetory +Operating System User Directory ================ ========================================= -GNU/Linux ```` +GNU/Linux ``~/.config/openmw/`` OS X ``~/Library/Application Support/openmw/`` -Windows ```` +Windows ``C:\Users\ *Username* \Documents\my games\OpenMW`` ================ ========================================= In addition to to this single hard-coded directory both OpenMW and OpenMW CS -need a place to seek for a actuals data files of the game: textures, 3D models, -sounds and record files that store objects in game; dialogues an so one. These +need a place to search for actual data files of the game: textures, 3D models, +sounds and record files that store objects in game; dialogues and so on. These files are called *content files*. We support multiple such paths (we call them *data paths*) as specified in the configuration. Usually one data path points to the directory where the original Morrowind game is either installed or @@ -42,12 +42,12 @@ Content files ============= The original Morrowind engine by Bethesda Softworks uses two types of content -files: `esm` (master) and `esp` (plugin). The distinction between those two is -not clear, and often confusing. One would expect the `esm` (master) file to be -used to specify one master, which is then modified by the `esp` plugins. And +files: `ESM` (master) and `ESP` (plugin). The distinction between those two is +not clear, and often confusing. One would expect the `ESM` (master) file to be +used to specify one master, which is then modified by the `ESP` plugins. And indeed: this is the basic idea. However, the official expansions were also made as ESM files, even though they could essentially be described as really large -plugins, and therefore would rather use `esp` files. There were technical +plugins, and therefore should have been `ESP` files. There were technical reasons behind this decision – somewhat valid in the case of the original engine, but clearly it is better to create a system that can be used in a more sensible way. OpenMW achieves this with our own content file types. @@ -62,7 +62,7 @@ OpenMW content files The concepts of *Game* and *Addon* files are somewhat similar to the old concept of *ESM* and *ESP*, but more strictly enforced. It is quite -straight-formward: If you want to make new game using OpenMW as the engine (a +straight-forward: If you want to make new game using OpenMW as the engine (a so called *total conversion*) you should create a game file. If you want to create an addon for an existing game file create an addon file. Nothing else matters; the only distinction you should consider is if your project is about @@ -75,21 +75,21 @@ Another simple thing about content files are the extensions: we are using Morrowind content files ----------------------- -Using our content files is recommended for projects that are intended to used -with the OpenMW engine. However, some players might wish to still use the +Using our content files is recommended for projects that are intended to use +the OpenMW engine. However, some players might wish to still use the original Morrowind engine. In addition thousands of *ESP*/*ESM* files were created since 2002, some of them with really outstanding content. Because of this OpenMW CS simply has no other choice but to support *ESP*/*ESM* files. If -you decid to choose *ESP*/*ESM* file instead of using our own content file -types you are most likely aimng at compatibility with the original engine. This -subject is covered in it own chapter of this manual. +you decide to choose *ESP*/*ESM* file instead of using our own content file +types you are most likely aiming at compatibility with the original engine. This +subject is covered in its own chapter of this manual. .. TODO This paragraph sounds weird The actual creation of new files is described in the next chapter. Here we are going to focus only on the details you need to know in order to create your -first OpenMW CS file while fully understanding your needs. For now let’s jut +first OpenMW CS file while fully understanding your needs. For now let’s just remember that content files are created inside the user directory in the the ``data`` subdirectory (that is the one special data directory mentioned earlier). @@ -99,8 +99,8 @@ Dependencies ------------ Since an addon is supposed to change the game it follows that it also depends -on the said game to work. We can conceptualise this with an examples: your -modification is the changing prize of an iron sword, but what if there is no +on the said game to work. We can conceptualise this with an example: your +modification is changing the price of an iron sword, but what if there is no iron sword in game? That's right: we get nonsense. What you want to do is tie your addon to the files you are changing. Those can be either game files (for example when making an expansion island for a game) or other addon files @@ -112,9 +112,9 @@ files – it is only a theoretical introduction to the subject. For now just kee in mind that dependencies exist, and is up to you to decide whether your content file should depend on other content files. -Game files are not intend to have any dependencies for a very simple reasons: +Game files are not intended to have any dependencies for a very simple reasons: the player is using only one game file (excluding original and the dirty -ESP/ESM system) at a time and therefore no game file can depend on other game +ESP/ESM system) at a time and therefore no game file can depend on another game file, and since a game file makes the base for addon files it can not depend on addon files. @@ -123,7 +123,7 @@ Project files ------------- Project files act as containers for data not used by the OpenMW game engine -itself, but still useful for OpenMW CS. The shining example of this data +itself, but still useful for OpenMW CS. The shining examples of this data category are without doubt record filters (described in a later chapter of the manual). As a mod author you probably do not need or want to distribute project files at all, they are meant to be used only by you and your team. @@ -132,7 +132,7 @@ files at all, they are meant to be used only by you and your team. As you would imagine, project files make sense only in combination with actual content files. In fact, each time you start to work on new content file and a -project file was not found, one will be created. The extensio of project files +project file was not found, one will be created. The extension of project files is ``.project``. The whole name of the project file is the whole name of the content file with appended extension. For instance a ``swords.omwaddon`` file is associated with a ``swords.omwaddon.project`` file. diff --git a/docs/source/manuals/openmw-cs/starting-dialog.rst b/docs/source/manuals/openmw-cs/starting-dialog.rst index 02a65ff21..fa069d8d6 100644 --- a/docs/source/manuals/openmw-cs/starting-dialog.rst +++ b/docs/source/manuals/openmw-cs/starting-dialog.rst @@ -3,7 +3,7 @@ OpenMW CS Starting Dialog In this chapter we will cover starting up OpenMW CS and the starting interface. Start the CS the way intended for your operating system and you will be -presented with window and three main buttons and a small button with a +presented with a window and three main buttons and a small button with a wrench-icon. The wrench will open the configuration dialog which we will cover later. The three main buttons are the following: @@ -32,7 +32,7 @@ choose exactly one game and you can choose an arbitrary amount of addon dependencies. For the sake of simplicity and maintainability choose only the addons you actually want to depend on. Also keep in mind that your dependencies might have dependencies of their own, you have to depend on those as well. If -one of your dependencies nees something it will be indicated by a warning sign +one of your dependencies needs something it will be indicated by a warning sign and automatically include its dependencies when you choose it. If you want to edit an existing content file you will be presented with a diff --git a/docs/source/manuals/openmw-cs/tour.rst b/docs/source/manuals/openmw-cs/tour.rst index 9844948ea..bb1097e0c 100644 --- a/docs/source/manuals/openmw-cs/tour.rst +++ b/docs/source/manuals/openmw-cs/tour.rst @@ -48,7 +48,7 @@ Once the addon has been created you will be presented with a table. If you see a blank window rather than a table choose *World* → *Objects* from the menu. .. figure:: _static/images/chapter-1/objects.png - :alt: The table showing all objet records in the game. + :alt: The table showing all object records in the game. Let's talk about the interface for a second. Every window in OpenMW CS has *panels*, these are often but not always tables. You can close a panel by @@ -139,7 +139,7 @@ the first character. Type the following into the field: A filter is defined by a number of *queries* which can be logically linked. For now all that matters is that the `string(, )` query will check -whether `` matches ``. The pattern is a regular expression, +whether `` matches ``. The pattern is a regular expression, if you don't know about them you should learn their syntax. For now all that matters is that `.` stands for any character and `*` stands for any amount, even zero. In other words, we are looking for all entries which have an ID that diff --git a/docs/source/reference/modding/convert_bump_mapped_mods.rst b/docs/source/reference/modding/convert_bump_mapped_mods.rst index 791e77353..71ac29468 100644 --- a/docs/source/reference/modding/convert_bump_mapped_mods.rst +++ b/docs/source/reference/modding/convert_bump_mapped_mods.rst @@ -176,7 +176,7 @@ The sacks included in Apel's `Various Things - Sacks`_ come in two versions – #. Open up each of the models in NifSkope and look for these certain blocks_: - NiTextureEffect - NiSourceTexture with the value that appears to be a normal map file, in this mod, they have the suffix *_nm.dds*. -#. Remove all these tags by selecting them one at a time and press right click>Block>Remove. +#. Remove all these tags by selecting them one at a time and press right click>Block>Remove Branch. (Ctrl-Del) #. Repeat this on all the affected models. #. If you launch OpenMW now, you'll `no longer have shiny models`_. But one thing is missing. Can you see it? It's actually hard to spot on still pictures, but we have no normal maps here. #. Now, go back to the root of where you installed the mod. Now go to ``./Textures/`` and you'll find the texture files in question. diff --git a/docs/source/reference/modding/settings/game.rst b/docs/source/reference/modding/settings/game.rst index 62fe5a70e..e1d5d75f6 100644 --- a/docs/source/reference/modding/settings/game.rst +++ b/docs/source/reference/modding/settings/game.rst @@ -65,6 +65,22 @@ the type of attack is determined by the direction that the character is moving a The default value is false. This setting can be toggled with the Always Use Best Attack button in the Prefs panel of the Options menu. +can loot during death animation +------------------------------- + +:Type: boolean +:Range: True/False +:Default: True + +If this setting is true, the player is allowed to loot actors (e.g. summoned creatures) during death animation, if they are not in combat. +However disposing corpses during death animation is not recommended - death counter may not be incremented, and this behaviour can break quests. +This is how original Morrowind behaves. + +If this setting is false, player has to wait until end of death animation in all cases. +This case is more safe, but makes using of summoned creatures exploit (looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder. + +The default value is true. This setting can only be configured by editing the settings configuration file. + difficulty ---------- @@ -110,4 +126,4 @@ followers attack on sight :Default: False Makes player followers and escorters start combat with enemies who have started combat with them or the player. -Otherwise they wait for the enemies or the player to do an attack first. \ No newline at end of file +Otherwise they wait for the enemies or the player to do an attack first. diff --git a/files/mygui/CMakeLists.txt b/files/mygui/CMakeLists.txt index 8f28977a8..0f5e7b5e5 100644 --- a/files/mygui/CMakeLists.txt +++ b/files/mygui/CMakeLists.txt @@ -38,6 +38,7 @@ set(MYGUI_FILES openmw_hud.layout openmw_infobox.layout openmw_interactive_messagebox.layout + openmw_interactive_messagebox_notransp.layout openmw_inventory_window.layout openmw_journal.layout openmw_journal.skin.xml diff --git a/files/mygui/openmw_chargen_class.layout b/files/mygui/openmw_chargen_class.layout index 35dd57ca3..d875fae22 100644 --- a/files/mygui/openmw_chargen_class.layout +++ b/files/mygui/openmw_chargen_class.layout @@ -34,6 +34,7 @@ + diff --git a/files/mygui/openmw_chargen_class_description.layout b/files/mygui/openmw_chargen_class_description.layout index 3d341eee5..43b0518fd 100644 --- a/files/mygui/openmw_chargen_class_description.layout +++ b/files/mygui/openmw_chargen_class_description.layout @@ -1,7 +1,7 @@ - + diff --git a/files/mygui/openmw_chargen_create_class.layout b/files/mygui/openmw_chargen_create_class.layout index 9ca6c6a31..e2920c742 100644 --- a/files/mygui/openmw_chargen_create_class.layout +++ b/files/mygui/openmw_chargen_create_class.layout @@ -33,6 +33,7 @@ + diff --git a/files/mygui/openmw_edit_note.layout b/files/mygui/openmw_edit_note.layout index eb2a2789d..7039c719e 100644 --- a/files/mygui/openmw_edit_note.layout +++ b/files/mygui/openmw_edit_note.layout @@ -1,7 +1,7 @@ - + diff --git a/files/mygui/openmw_hud_box.skin.xml b/files/mygui/openmw_hud_box.skin.xml index 33199d6ae..e53493bb1 100644 --- a/files/mygui/openmw_hud_box.skin.xml +++ b/files/mygui/openmw_hud_box.skin.xml @@ -36,6 +36,18 @@ + + + + + + + + + + + + diff --git a/files/mygui/openmw_interactive_messagebox_notransp.layout b/files/mygui/openmw_interactive_messagebox_notransp.layout new file mode 100644 index 000000000..6b79b9417 --- /dev/null +++ b/files/mygui/openmw_interactive_messagebox_notransp.layout @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/files/mygui/openmw_inventory_window.layout b/files/mygui/openmw_inventory_window.layout index 09e5ed9c7..bb707fa0d 100644 --- a/files/mygui/openmw_inventory_window.layout +++ b/files/mygui/openmw_inventory_window.layout @@ -12,7 +12,7 @@ - + diff --git a/files/mygui/openmw_journal.layout b/files/mygui/openmw_journal.layout index c53f39578..1131b1bbc 100644 --- a/files/mygui/openmw_journal.layout +++ b/files/mygui/openmw_journal.layout @@ -49,33 +49,33 @@ - - + + - + - + - + - + - + @@ -88,7 +88,7 @@ - + diff --git a/files/mygui/openmw_persuasion_dialog.layout b/files/mygui/openmw_persuasion_dialog.layout index d9a673ec2..c66f9d0ad 100644 --- a/files/mygui/openmw_persuasion_dialog.layout +++ b/files/mygui/openmw_persuasion_dialog.layout @@ -1,7 +1,7 @@ - + diff --git a/files/mygui/openmw_windows.skin.xml b/files/mygui/openmw_windows.skin.xml index 00e6f9148..a272ae84a 100644 --- a/files/mygui/openmw_windows.skin.xml +++ b/files/mygui/openmw_windows.skin.xml @@ -158,13 +158,20 @@ - + + + + + + + + diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 40c1ed099..a0460326b 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -180,6 +180,9 @@ prevent merchant equipping = false # or the player. Otherwise they wait for the enemies or the player to do an attack first. followers attack on sight = false +# Can loot non-fighting actors during death animation +can loot during death animation = true + [General] # Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16). diff --git a/files/ui/wizard/methodselectionpage.ui b/files/ui/wizard/methodselectionpage.ui index 531d093af..4d4d66bad 100644 --- a/files/ui/wizard/methodselectionpage.ui +++ b/files/ui/wizard/methodselectionpage.ui @@ -17,16 +17,16 @@ Select Installation Method - Select how OpenMW should get the required Morrowind installation files. + <html><head/><body><p>Select how you would like to install <i>The Elder Scrolls III: Morrowind</i>.</p></body></html> - + font-weight:bold; - Install Morrowind to a new location + Retail CD/DVD true @@ -34,7 +34,7 @@ - + @@ -72,14 +72,14 @@ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - newLocationRadioButton + retailDiscRadioButton - + - Install Morrowind from a retail disk to a new location for OpenMW to use. + Install from a retail disc to a new location. true @@ -94,7 +94,7 @@ font-weight:bold - Select an existing Morrowind installation + Existing Installation @@ -138,7 +138,7 @@ - Select an existing Morrowind installation for OpenMW to use. + Select an existing installation. true