diff --git a/.travis.yml b/.travis.yml index 042d4b8f1a..1be8aa59cc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,7 +31,7 @@ before_script: script: - cd ./build - if [ "$COVERITY_SCAN_BRANCH" != 1 ]; then make -j4; fi - - if [ "$COVERITY_SCAN_BRANCH" != 1 ] && ["${TRAVIS_OS_NAME}" = "osx"]; then make package; fi + - if [ "$COVERITY_SCAN_BRANCH" != 1 ] && [ "${TRAVIS_OS_NAME}" = "osx" ]; then make package; fi after_script: - if [ "${TRAVIS_OS_NAME}" = "linux" ]; then ./openmw_test_suite; fi notifications: diff --git a/AUTHORS.md b/AUTHORS.md index 540c0ede5a..90c543adae 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -39,6 +39,7 @@ Programmers Eli2 Emanuel Guével (potatoesmaster) eroen + Evgeniy Mineev (sandstranger) Fil Krynicki (filkry) Gašper Sedej gugus/gus @@ -91,7 +92,6 @@ Programmers Rohit Nirmal Roman Melnik (Kromgart) Roman Proskuryakov (humbug) - sandstranger Sandy Carter (bwrsandman) Scott Howard Sebastian Wick (swick) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2a117d42d3..0c3d7c3783 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -672,6 +672,7 @@ if (WIN32) 4193 # #pragma warning(pop) : no matching '#pragma warning(push)' 4251 # class 'XXXX' needs to have dll-interface to be used by clients of class 'YYYY' 4275 # non dll-interface struct 'XXXX' used as base for dll-interface class 'YYYY' + 4315 # undocumented, 'this' pointer for member might not be aligned (OgreMemoryStlAllocator.h) # caused by boost 4191 # 'type cast' : unsafe conversion (1.56, thread_primitives.hpp, normally off) @@ -679,6 +680,7 @@ if (WIN32) # OpenMW specific warnings 4099 # Type mismatch, declared class or struct is defined with other type 4100 # Unreferenced formal parameter (-Wunused-parameter) + 4101 # Unreferenced local variable (-Wunused-variable) 4127 # Conditional expression is constant 4242 # Storing value in a variable of a smaller type, possible loss of data 4244 # Storing value of one type in variable of another (size_t in int, for example) @@ -700,14 +702,16 @@ if (WIN32) # boost::wave has a few issues with signed / unsigned conversions, so we suppress those here set(SHINY_WARNINGS "${WARNINGS} /wd4245") set_target_properties(shiny PROPERTIES COMPILE_FLAGS "${SHINY_WARNINGS} ${MT_BUILD}") - # there's an unreferenced local variable in the ogre platform, suppress it - set(SHINY_OGRE_WARNINGS "${WARNINGS} /wd4101") - set_target_properties(shiny.OgrePlatform PROPERTIES COMPILE_FLAGS "${SHINY_OGRE_WARNINGS} ${MT_BUILD}") + set_target_properties(shiny.OgrePlatform PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") set_target_properties(sdl4ogre PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") # oics uses tinyxml, which has an initialized but unused variable set(OICS_WARNINGS "${WARNINGS} /wd4189") set_target_properties(oics PROPERTIES COMPILE_FLAGS "${OICS_WARNINGS} ${MT_BUILD}") set_target_properties(components PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") + set_target_properties(ogre-ffmpeg-videoplayer PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") + if (BUILD_MYGUI_PLUGIN) + set_target_properties(Plugin_MyGUI_OpenMW_Resources PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") + endif (BUILD_MYGUI_PLUGIN) if (BUILD_LAUNCHER) set_target_properties(openmw-launcher PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") endif (BUILD_LAUNCHER) @@ -726,6 +730,9 @@ if (WIN32) set(OPENCS_WARNINGS "${WARNINGS} ${MT_BUILD} /wd4435") set_target_properties(openmw-cs PROPERTIES COMPILE_FLAGS ${OPENCS_WARNINGS}) endif (BUILD_OPENCS) + if (BUILD_ESSIMPORTER) + set_target_properties(openmw-essimporter PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") + endif (BUILD_ESSIMPORTER) if (BUILD_MWINIIMPORTER) set_target_properties(openmw-iniimporter PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") endif (BUILD_MWINIIMPORTER) diff --git a/apps/essimporter/importacdt.hpp b/apps/essimporter/importacdt.hpp index 47055092de..eacb2edf1a 100644 --- a/apps/essimporter/importacdt.hpp +++ b/apps/essimporter/importacdt.hpp @@ -43,13 +43,17 @@ namespace ESSImport float mMagicEffects[27]; // Effect attributes: https://wiki.openmw.org/index.php?title=Research:Magic#Effect_attributes unsigned char mUnknown4[4]; unsigned int mGoldPool; - unsigned char mUnknown5[4]; + unsigned char mCountDown; // seen the same value as in ACSC.mCorpseClearCountdown, maybe + // this one is for respawning? + unsigned char mUnknown5[3]; }; struct ACSC { unsigned char mUnknown1[17]; unsigned char mFlags; // ACSCFlags - unsigned char mUnknown2[94]; + unsigned char mUnknown2[22]; + unsigned char mCorpseClearCountdown; // hours? + unsigned char mUnknown3[71]; }; #pragma pack(pop) diff --git a/apps/essimporter/importcrec.cpp b/apps/essimporter/importcrec.cpp index 7a8a3eb00d..64879f2afc 100644 --- a/apps/essimporter/importcrec.cpp +++ b/apps/essimporter/importcrec.cpp @@ -14,10 +14,10 @@ namespace ESSImport float scale; esm.getHNOT(scale, "XSCL"); - // FIXME: use AiPackageList, need to fix getSubName() + while (esm.isNextSub("AI_W") || esm.isNextSub("AI_E") || esm.isNextSub("AI_T") || esm.isNextSub("AI_F") || esm.isNextSub("AI_A")) - esm.skipHSub(); + mAiPackages.add(esm); mInventory.load(esm); } diff --git a/apps/essimporter/importcrec.hpp b/apps/essimporter/importcrec.hpp index 16b7528070..5110fbc689 100644 --- a/apps/essimporter/importcrec.hpp +++ b/apps/essimporter/importcrec.hpp @@ -2,6 +2,7 @@ #define OPENMW_ESSIMPORT_CREC_H #include "importinventory.hpp" +#include namespace ESM { @@ -17,6 +18,7 @@ namespace ESSImport int mIndex; Inventory mInventory; + ESM::AIPackageList mAiPackages; void load(ESM::ESMReader& esm); }; diff --git a/apps/essimporter/importnpcc.cpp b/apps/essimporter/importnpcc.cpp index 547b014413..3cbd749ce8 100644 --- a/apps/essimporter/importnpcc.cpp +++ b/apps/essimporter/importnpcc.cpp @@ -9,10 +9,9 @@ namespace ESSImport { esm.getHNT(mNPDT, "NPDT"); - // FIXME: use AiPackageList, need to fix getSubName() while (esm.isNextSub("AI_W") || esm.isNextSub("AI_E") || esm.isNextSub("AI_T") || esm.isNextSub("AI_F") || esm.isNextSub("AI_A")) - esm.skipHSub(); + mAiPackages.add(esm); mInventory.load(esm); } diff --git a/apps/essimporter/importnpcc.hpp b/apps/essimporter/importnpcc.hpp index c69fa3e035..a23ab1e50b 100644 --- a/apps/essimporter/importnpcc.hpp +++ b/apps/essimporter/importnpcc.hpp @@ -27,6 +27,7 @@ namespace ESSImport } mNPDT; Inventory mInventory; + ESM::AIPackageList mAiPackages; void load(ESM::ESMReader &esm); }; diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index b93d55c176..4c142231d5 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -61,6 +61,7 @@ Launcher::MainDialog::MainDialog(QWidget *parent) QString revision(OPENMW_VERSION_COMMITHASH); QString tag(OPENMW_VERSION_TAGHASH); + versionLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); if (!revision.isEmpty() && !tag.isEmpty()) { if (revision == tag) { @@ -238,24 +239,8 @@ void Launcher::MainDialog::changePage(QListWidgetItem *current, QListWidgetItem current = previous; int currentIndex = iconWidget->row(current); -// int previousIndex = iconWidget->row(previous); - pagesWidget->setCurrentIndex(currentIndex); - - // DataFilesPage *previousPage = dynamic_cast(pagesWidget->widget(previousIndex)); - // DataFilesPage *currentPage = dynamic_cast(pagesWidget->widget(currentIndex)); - - // //special call to update/save data files page list view when it's displayed/hidden. - // if (previousPage) - // { - // if (previousPage->objectName() == "DataFilesPage") - // previousPage->saveSettings(); - // } - // else if (currentPage) - // { - // if (currentPage->objectName() == "DataFilesPage") - // currentPage->loadSettings(); - // } + mSettingsPage->resetProgressBar(); } bool Launcher::MainDialog::setupLauncherSettings() diff --git a/apps/launcher/settingspage.cpp b/apps/launcher/settingspage.cpp index c172a31210..34b4b41a9e 100644 --- a/apps/launcher/settingspage.cpp +++ b/apps/launcher/settingspage.cpp @@ -39,6 +39,7 @@ Launcher::SettingsPage::SettingsPage(Files::ConfigurationManager &cfg, mWizardInvoker = new ProcessInvoker(); mImporterInvoker = new ProcessInvoker(); + resetProgressBar(); connect(mWizardInvoker->getProcess(), SIGNAL(started()), this, SLOT(wizardStarted())); @@ -94,7 +95,7 @@ Launcher::SettingsPage::~SettingsPage() void Launcher::SettingsPage::on_wizardButton_clicked() { - saveSettings(); + mMain->writeSettings(); if (!mWizardInvoker->startProcess(QLatin1String("openmw-wizard"), false)) return; @@ -102,7 +103,7 @@ void Launcher::SettingsPage::on_wizardButton_clicked() void Launcher::SettingsPage::on_importerButton_clicked() { - saveSettings(); + mMain->writeSettings(); // Create the file if it doesn't already exist, else the importer will fail QString path(QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str())); @@ -141,8 +142,13 @@ void Launcher::SettingsPage::on_importerButton_clicked() qDebug() << "arguments " << arguments; + // start the progress bar as a "bouncing ball" + progressBar->setMaximum(0); + progressBar->setValue(0); if (!mImporterInvoker->startProcess(QLatin1String("openmw-iniimporter"), arguments, false)) - return; + { + resetProgressBar(); + } } void Launcher::SettingsPage::on_browseButton_clicked() @@ -197,38 +203,35 @@ void Launcher::SettingsPage::importerStarted() void Launcher::SettingsPage::importerFinished(int exitCode, QProcess::ExitStatus exitStatus) { if (exitCode != 0 || exitStatus == QProcess::CrashExit) - return; - - // Importer may have changed settings, so refresh - mMain->reloadSettings(); - - // Import selected data files from openmw.cfg - if (addonsCheckBox->isChecked()) { - // Because we've reloaded settings, the current content list matches content in OpenMW.cfg - QString oldContentListName = mLauncherSettings.getCurrentContentListName(); - if (mProfileDialog->exec() == QDialog::Accepted) - { - // remove the current content list to prevent duplication - //... except, not allowed to delete the Default content list - if (oldContentListName.compare(DataFilesPage::mDefaultContentListName) != 0) - { - mLauncherSettings.removeContentList(oldContentListName); - } + resetProgressBar(); - const QString newContentListName(mProfileDialog->lineEdit()->text()); - const QStringList files(mGameSettings.getContentList()); - mLauncherSettings.setCurrentContentListName(newContentListName); - mLauncherSettings.setContentList(newContentListName, files); + QMessageBox msgBox; + msgBox.setWindowTitle(tr("Importer finished")); + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setIcon(QMessageBox::Warning); + msgBox.setText(tr("Failed to import settings from INI file.")); + msgBox.exec(); + } + else + { + // indicate progress finished + progressBar->setMaximum(1); + progressBar->setValue(1); - // Make DataFiles Page load the new content list. - mMain->reloadSettings(); - } + // Importer may have changed settings, so refresh + mMain->reloadSettings(); } importerButton->setEnabled(true); } +void Launcher::SettingsPage::resetProgressBar() +{ + // set progress bar to 0 % + progressBar->reset(); +} + void Launcher::SettingsPage::updateOkButton(const QString &text) { // We do this here because we need to access the profiles diff --git a/apps/launcher/settingspage.hpp b/apps/launcher/settingspage.hpp index 124c806009..ccc2061dd7 100644 --- a/apps/launcher/settingspage.hpp +++ b/apps/launcher/settingspage.hpp @@ -29,6 +29,9 @@ namespace Launcher void saveSettings(); bool loadSettings(); + + /// set progress bar on page to 0% + void resetProgressBar(); private slots: @@ -57,7 +60,6 @@ namespace Launcher MainDialog *mMain; TextInputDialog *mProfileDialog; - }; } diff --git a/apps/mwiniimporter/importer.cpp b/apps/mwiniimporter/importer.cpp index 80f186b1f4..efebe5a4d0 100644 --- a/apps/mwiniimporter/importer.cpp +++ b/apps/mwiniimporter/importer.cpp @@ -8,7 +8,8 @@ #include #include -#include +#include +#include #include namespace bfs = boost::filesystem; @@ -660,7 +661,7 @@ std::string MwIniImporter::numberToString(int n) { return str.str(); } -MwIniImporter::multistrmap MwIniImporter::loadIniFile(const std::string& filename) const { +MwIniImporter::multistrmap MwIniImporter::loadIniFile(const boost::filesystem::path& filename) const { std::cout << "load ini file: " << filename << std::endl; std::string section(""); @@ -719,7 +720,7 @@ MwIniImporter::multistrmap MwIniImporter::loadIniFile(const std::string& filenam return map; } -MwIniImporter::multistrmap MwIniImporter::loadCfgFile(const std::string& filename) { +MwIniImporter::multistrmap MwIniImporter::loadCfgFile(const boost::filesystem::path& filename) { std::cout << "load cfg file: " << filename << std::endl; MwIniImporter::multistrmap map; @@ -825,10 +826,14 @@ void MwIniImporter::importArchives(multistrmap &cfg, const multistrmap &ini) con } } -void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini) const { - std::vector contentFiles; +void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini, const boost::filesystem::path& iniFilename) const { + std::vector > contentFiles; std::string baseGameFile("Game Files:GameFile"); std::string gameFile(""); + std::time_t defaultTime = 0; + + // assume the Game Files are all in a "Data Files" directory under the directory holding Morrowind.ini + const boost::filesystem::path gameFilesDir(iniFilename.parent_path() /= "Data Files"); multistrmap::const_iterator it = ini.begin(); for(int i=0; it != ini.end(); i++) { @@ -845,18 +850,20 @@ void MwIniImporter::importGameFiles(multistrmap &cfg, const multistrmap &ini) co Misc::StringUtils::toLower(filetype); if(filetype.compare("esm") == 0 || filetype.compare("esp") == 0) { - contentFiles.push_back(*entry); + boost::filesystem::path filepath(gameFilesDir); + filepath /= *entry; + contentFiles.push_back(std::make_pair(lastWriteTime(filepath, defaultTime), *entry)); } } - - gameFile = ""; } cfg.erase("content"); cfg.insert( std::make_pair("content", std::vector() ) ); - for(std::vector::const_iterator it=contentFiles.begin(); it!=contentFiles.end(); ++it) { - cfg["content"].push_back(*it); + // this will sort files by time order first, then alphabetical (maybe), I suspect non ASCII filenames will be stuffed. + sort(contentFiles.begin(), contentFiles.end()); + for(std::vector >::const_iterator it=contentFiles.begin(); it!=contentFiles.end(); ++it) { + cfg["content"].push_back(it->second); } } @@ -873,3 +880,27 @@ void MwIniImporter::setInputEncoding(const ToUTF8::FromType &encoding) { mEncoding = encoding; } + +std::time_t MwIniImporter::lastWriteTime(const boost::filesystem::path& filename, std::time_t defaultTime) +{ + std::time_t writeTime(defaultTime); + if (boost::filesystem::exists(filename)) + { + // FixMe: remove #if when Boost dependency for Linux builds updated + // This allows Linux to build until then +#if (BOOST_VERSION >= 104800) + // need to resolve any symlinks so that we get time of file, not symlink + boost::filesystem::path resolved = boost::filesystem::canonical(filename); +#else + boost::filesystem::path resolved = filename; +#endif + writeTime = boost::filesystem::last_write_time(resolved); + std::cout << "content file: " << resolved << " timestamp = (" << writeTime << + ") " << asctime(localtime(&writeTime)) << std::endl; + } + else + { + std::cout << "content file: " << filename << " not found" << std::endl; + } + return writeTime; +} \ No newline at end of file diff --git a/apps/mwiniimporter/importer.hpp b/apps/mwiniimporter/importer.hpp index 72b14ba75f..c73cc65b5e 100644 --- a/apps/mwiniimporter/importer.hpp +++ b/apps/mwiniimporter/importer.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include @@ -17,17 +18,22 @@ class MwIniImporter { MwIniImporter(); void setInputEncoding(const ToUTF8::FromType& encoding); void setVerbose(bool verbose); - multistrmap loadIniFile(const std::string& filename) const; - static multistrmap loadCfgFile(const std::string& filename); + multistrmap loadIniFile(const boost::filesystem::path& filename) const; + static multistrmap loadCfgFile(const boost::filesystem::path& filename); void merge(multistrmap &cfg, const multistrmap &ini) const; void mergeFallback(multistrmap &cfg, const multistrmap &ini) const; - void importGameFiles(multistrmap &cfg, const multistrmap &ini) const; + void importGameFiles(multistrmap &cfg, const multistrmap &ini, + const boost::filesystem::path& iniFilename) const; void importArchives(multistrmap &cfg, const multistrmap &ini) const; static void writeToFile(std::ostream &out, const multistrmap &cfg); private: static void insertMultistrmap(multistrmap &cfg, const std::string& key, const std::string& value); static std::string numberToString(int n); + + /// \return file's "last modified time", used in original MW to determine plug-in load order + static std::time_t lastWriteTime(const boost::filesystem::path& filename, std::time_t defaultTime); + bool mVerbose; strmap mMergeMap; std::vector mMergeFallback; diff --git a/apps/mwiniimporter/main.cpp b/apps/mwiniimporter/main.cpp index f108678f3f..3c48fedc9a 100644 --- a/apps/mwiniimporter/main.cpp +++ b/apps/mwiniimporter/main.cpp @@ -93,8 +93,8 @@ int wmain(int argc, wchar_t *wargv[]) { bpo::notify(vm); - std::string iniFile = vm["ini"].as(); - std::string cfgFile = vm["cfg"].as(); + boost::filesystem::path iniFile(vm["ini"].as()); + boost::filesystem::path cfgFile(vm["cfg"].as()); // if no output is given, write back to cfg file std::string outputFile(vm["output"].as()); @@ -123,7 +123,7 @@ int wmain(int argc, wchar_t *wargv[]) { importer.mergeFallback(cfg, ini); if(vm.count("game-files")) { - importer.importGameFiles(cfg, ini); + importer.importGameFiles(cfg, ini, iniFile); } if(!vm.count("no-archives")) { diff --git a/apps/opencs/model/tools/tools.cpp b/apps/opencs/model/tools/tools.cpp index 0c20bd17b4..e78758bb67 100644 --- a/apps/opencs/model/tools/tools.cpp +++ b/apps/opencs/model/tools/tools.cpp @@ -58,9 +58,6 @@ CSMDoc::Operation *CSMTools::Tools::getVerifier() mandatoryIds.push_back ("GameHour"); mandatoryIds.push_back ("Month"); mandatoryIds.push_back ("PCRace"); - mandatoryIds.push_back ("PCVampire"); - mandatoryIds.push_back ("PCWerewolf"); - mandatoryIds.push_back ("PCYear"); mVerifier->appendStage (new MandatoryIdStage (mData.getGlobals(), CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Globals), mandatoryIds)); diff --git a/apps/opencs/view/doc/adjusterwidget.cpp b/apps/opencs/view/doc/adjusterwidget.cpp index 09e58690fc..6571ad7c81 100644 --- a/apps/opencs/view/doc/adjusterwidget.cpp +++ b/apps/opencs/view/doc/adjusterwidget.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -72,8 +73,11 @@ void CSVDoc::AdjusterWidget::setName (const QString& name, bool addon) { boost::filesystem::path path (name.toUtf8().data()); - bool isLegacyPath = (path.extension() == ".esm" || - path.extension() == ".esp"); + std::string extension = path.extension().string(); + boost::algorithm::to_lower(extension); + + bool isLegacyPath = (extension == ".esm" || + extension == ".esp"); bool isFilePathChanged = (path.parent_path().string() != mLocalData.string()); diff --git a/apps/opencs/view/doc/startup.cpp b/apps/opencs/view/doc/startup.cpp index 799a07e14b..58a46c603b 100644 --- a/apps/opencs/view/doc/startup.cpp +++ b/apps/opencs/view/doc/startup.cpp @@ -96,7 +96,7 @@ QWidget *CSVDoc::StartupDialogue::createTools() CSVDoc::StartupDialogue::StartupDialogue() : mWidth (0), mColumn (2) { - setWindowTitle ("Open CS"); + setWindowTitle ("OpenMW-CS"); QVBoxLayout *layout = new QVBoxLayout (this); diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 8180d0b1ab..9eb272e1b8 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -268,6 +268,8 @@ namespace MWBase virtual MWWorld::Ptr getFacedObject() = 0; ///< Return pointer to the object the player is looking at, if it is within activation range + virtual float getMaxActivationDistance() = 0; + /// Returns a pointer to the object the provided object would hit (if within the /// specified distance), and the point where the hit occurs. This will attempt to /// use the "Head" node, or alternatively the "Bip01 Head" node as a basis. @@ -551,7 +553,7 @@ namespace MWBase virtual void spawnEffect (const std::string& model, const std::string& textureOverride, const Ogre::Vector3& worldPos) = 0; virtual void explodeSpell (const Ogre::Vector3& origin, const ESM::EffectList& effects, - const MWWorld::Ptr& caster, int rangeType, const std::string& id, const std::string& sourceName) = 0; + const MWWorld::Ptr& caster, ESM::RangeType rangeType, const std::string& id, const std::string& sourceName) = 0; virtual void activate (const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0; diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index 1f7ead6f56..a4e67e9b14 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -671,4 +671,14 @@ namespace MWGui mEnemyHealthTimer = -1; } + void HUD::customMarkerCreated(MyGUI::Widget *marker) + { + marker->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onMapClicked); + } + + void HUD::doorMarkerCreated(MyGUI::Widget *marker) + { + marker->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onMapClicked); + } + } diff --git a/apps/openmw/mwgui/hud.hpp b/apps/openmw/mwgui/hud.hpp index 41a535a089..263c087740 100644 --- a/apps/openmw/mwgui/hud.hpp +++ b/apps/openmw/mwgui/hud.hpp @@ -119,6 +119,10 @@ namespace MWGui void onMagicClicked(MyGUI::Widget* _sender); void onMapClicked(MyGUI::Widget* _sender); + // LocalMapBase + virtual void customMarkerCreated(MyGUI::Widget* marker); + virtual void doorMarkerCreated(MyGUI::Widget* marker); + void updateEnemyHealthBar(); void updatePositions(); diff --git a/apps/openmw/mwgui/jailscreen.cpp b/apps/openmw/mwgui/jailscreen.cpp index 548b1b604c..58873c5660 100644 --- a/apps/openmw/mwgui/jailscreen.cpp +++ b/apps/openmw/mwgui/jailscreen.cpp @@ -17,7 +17,7 @@ namespace MWGui { JailScreen::JailScreen() : WindowBase("openmw_jail_screen.layout"), - mTimeAdvancer(0.0125), + mTimeAdvancer(0.01), mDays(1), mFadeTimeRemaining(0) { @@ -39,7 +39,7 @@ namespace MWGui mFadeTimeRemaining = 0.5; setVisible(false); - mProgressBar->setScrollRange(days*24+1); + mProgressBar->setScrollRange(100+1); mProgressBar->setScrollPosition(0); mProgressBar->setTrackSize(0); } @@ -59,7 +59,7 @@ namespace MWGui MWBase::Environment::get().getWorld()->teleportToClosestMarker(player, "prisonmarker"); setVisible(true); - mTimeAdvancer.run(mDays*24); + mTimeAdvancer.run(100); } } diff --git a/apps/openmw/mwgui/journalviewmodel.cpp b/apps/openmw/mwgui/journalviewmodel.cpp index 3a86613f61..9a47070c2f 100644 --- a/apps/openmw/mwgui/journalviewmodel.cpp +++ b/apps/openmw/mwgui/journalviewmodel.cpp @@ -199,7 +199,7 @@ struct JournalViewModelImpl : JournalViewModel }; - void visitQuestNames (bool active_only, boost::function visitor) const + void visitQuestNames (bool active_only, boost::function visitor) const { MWBase::Journal * journal = MWBase::Environment::get ().getJournal (); @@ -231,7 +231,7 @@ struct JournalViewModelImpl : JournalViewModel if (visitedQuests.find(quest.getName()) != visitedQuests.end()) continue; - visitor (quest.getName()); + visitor (quest.getName(), isFinished); visitedQuests.insert(quest.getName()); } diff --git a/apps/openmw/mwgui/journalviewmodel.hpp b/apps/openmw/mwgui/journalviewmodel.hpp index 5f0189b590..b3c6b0183c 100644 --- a/apps/openmw/mwgui/journalviewmodel.hpp +++ b/apps/openmw/mwgui/journalviewmodel.hpp @@ -67,8 +67,8 @@ namespace MWGui /// returns true if their are no journal entries to display virtual bool isEmpty () const = 0; - /// walks the active and optionally completed, quests providing the name - virtual void visitQuestNames (bool active_only, boost::function visitor) const = 0; + /// walks the active and optionally completed, quests providing the name and completed status + virtual void visitQuestNames (bool active_only, boost::function visitor) const = 0; /// walks over the journal entries related to all quests with the given name /// If \a questName is empty, simply visits all journal entries diff --git a/apps/openmw/mwgui/journalwindow.cpp b/apps/openmw/mwgui/journalwindow.cpp index d7e27a2776..4cfcc80641 100644 --- a/apps/openmw/mwgui/journalwindow.cpp +++ b/apps/openmw/mwgui/journalwindow.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -428,11 +429,24 @@ namespace AddNamesToList(Gui::MWList* list) : mList(list) {} Gui::MWList* mList; - void operator () (const std::string& name) + void operator () (const std::string& name, bool finished=false) { mList->addItem(name); } }; + struct SetNamesInactive + { + SetNamesInactive(Gui::MWList* list) : mList(list) {} + + Gui::MWList* mList; + void operator () (const std::string& name, bool finished) + { + if (finished) + { + mList->getItemWidget(name)->setStateSelected(true); + } + } + }; void notifyQuests(MyGUI::Widget* _sender) { @@ -453,6 +467,12 @@ namespace mModel->visitQuestNames(!mAllQuests, add); list->adjustSize(); + + if (mAllQuests) + { + SetNamesInactive setInactive(list); + mModel->visitQuestNames(!mAllQuests, setInactive); + } } void notifyShowAll(MyGUI::Widget* _sender) diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp index 69365c1081..cc032691e2 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -102,6 +102,31 @@ namespace MWGui updateSpells(); } + void SpellWindow::askDeleteSpell(const std::string &spellId) + { + // delete spell, if allowed + const ESM::Spell* spell = + MWBase::Environment::get().getWorld()->getStore().get().find(spellId); + + if (spell->mData.mFlags & ESM::Spell::F_Always + || spell->mData.mType == ESM::Spell::ST_Power) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sDeleteSpellError}"); + } + else + { + // ask for confirmation + mSpellToDelete = spellId; + ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog(); + std::string question = MWBase::Environment::get().getWindowManager()->getGameSettingString("sQuestionDeleteSpell", "Delete %s?"); + question = boost::str(boost::format(question) % spell->mName); + dialog->open(question); + dialog->eventOkClicked.clear(); + dialog->eventOkClicked += MyGUI::newDelegate(this, &SpellWindow::onDeleteSpellAccept); + dialog->eventCancelClicked.clear(); + } + } + void SpellWindow::onModelIndexSelected(SpellModel::ModelIndex index) { const Spell& spell = mSpellView->getModel()->getItem(index); @@ -111,43 +136,19 @@ namespace MWGui } else { - onSpellSelected(spell.mId); + if (MyGUI::InputManager::getInstance().isShiftPressed()) + askDeleteSpell(spell.mId); + else + onSpellSelected(spell.mId); } } void SpellWindow::onSpellSelected(const std::string& spellId) { - if (MyGUI::InputManager::getInstance().isShiftPressed()) - { - // delete spell, if allowed - const ESM::Spell* spell = - MWBase::Environment::get().getWorld()->getStore().get().find(spellId); - - if (spell->mData.mFlags & ESM::Spell::F_Always - || spell->mData.mType == ESM::Spell::ST_Power) - { - MWBase::Environment::get().getWindowManager()->messageBox("#{sDeleteSpellError}"); - } - else - { - // ask for confirmation - mSpellToDelete = spellId; - ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog(); - std::string question = MWBase::Environment::get().getWindowManager()->getGameSettingString("sQuestionDeleteSpell", "Delete %s?"); - question = boost::str(boost::format(question) % spell->mName); - dialog->open(question); - dialog->eventOkClicked.clear(); - dialog->eventOkClicked += MyGUI::newDelegate(this, &SpellWindow::onDeleteSpellAccept); - dialog->eventCancelClicked.clear(); - } - } - else - { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); - store.setSelectedEnchantItem(store.end()); - MWBase::Environment::get().getWindowManager()->setSelectedSpell(spellId, int(MWMechanics::getSpellSuccessChance(spellId, player))); - } + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); + store.setSelectedEnchantItem(store.end()); + MWBase::Environment::get().getWindowManager()->setSelectedSpell(spellId, int(MWMechanics::getSpellSuccessChance(spellId, player))); updateSpells(); } @@ -184,6 +185,10 @@ namespace MWGui return; selected = (selected + itemcount) % itemcount; - onModelIndexSelected(selected); + const Spell& spell = mSpellView->getModel()->getItem(selected); + if (spell.mType == Spell::Type_EnchantedItem) + onEnchantedItemSelected(spell.mItem, spell.mActive); + else + onSpellSelected(spell.mId); } } diff --git a/apps/openmw/mwgui/spellwindow.hpp b/apps/openmw/mwgui/spellwindow.hpp index 650218d307..8b5474f58c 100644 --- a/apps/openmw/mwgui/spellwindow.hpp +++ b/apps/openmw/mwgui/spellwindow.hpp @@ -33,6 +33,7 @@ namespace MWGui void onSpellSelected(const std::string& spellId); void onModelIndexSelected(SpellModel::ModelIndex index); void onDeleteSpellAccept(); + void askDeleteSpell(const std::string& spellId); virtual void onPinToggled(); virtual void onTitleDoubleClicked(); diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index f2b125add6..fd4fd293d6 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -207,16 +207,6 @@ namespace MWMechanics const MWWorld::Class& actorClass = actor.getClass(); MWBase::World* world = MWBase::Environment::get().getWorld(); - // can't fight if attacker can't go where target is. E.g. A fish can't attack person on land. - if (!actorClass.isNpc() && !MWMechanics::isEnvironmentCompatible(actor, target)) - { - actorClass.getCreatureStats(actor).setAttackingOrSpell(false); - return true; - } - - - - //Update every frame bool& combatMove = storage.mCombatMove; @@ -476,6 +466,19 @@ namespace MWMechanics // for distant combat we should know if target is in LOS even if distToTarget < rangeAttack bool inLOS = distantCombat ? world->getLOS(actor, target) : true; + // can't fight if attacker can't go where target is. E.g. A fish can't attack person on land. + if (distToTarget >= rangeAttack + && !actorClass.isNpc() && !MWMechanics::isEnvironmentCompatible(actor, target)) + { + // TODO: start fleeing? + movement.mPosition[0] = 0; + movement.mPosition[1] = 0; + movement.mPosition[2] = 0; + readyToAttack = false; + actorClass.getCreatureStats(actor).setAttackingOrSpell(false); + return false; + } + // (within attack dist) || (not quite attack dist while following) if(inLOS && (distToTarget < rangeAttack || (distToTarget <= rangeFollow && followTarget && !isStuck))) { diff --git a/apps/openmw/mwmechanics/magiceffects.hpp b/apps/openmw/mwmechanics/magiceffects.hpp index c384d0857f..86f5a1804a 100644 --- a/apps/openmw/mwmechanics/magiceffects.hpp +++ b/apps/openmw/mwmechanics/magiceffects.hpp @@ -72,6 +72,8 @@ namespace MWMechanics // Used by effect management classes (ActiveSpells, InventoryStore, Spells) to list active effect sources for GUI display struct EffectSourceVisitor { + virtual ~EffectSourceVisitor() { } + virtual void visit (MWMechanics::EffectKey key, const std::string& sourceName, const std::string& sourceId, int casterActorId, float magnitude, float remainingTime = -1, float totalTime = -1) = 0; diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 49887e560f..105fa58664 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -309,9 +309,11 @@ namespace MWMechanics for (std::vector::const_iterator iter (effects.mList.begin()); iter!=effects.mList.end(); ++iter) { - if (iter->mRange != range) - continue; - found = true; + if (iter->mRange == range) + { + found = true; + break; + } } if (!found) return; @@ -592,6 +594,7 @@ namespace MWMechanics value.restore(magnitude); target.getClass().getCreatureStats(target).setAttribute(attribute, value); } + // TODO: refactor the effect tick functions in Actors so they can be reused here else if (effectId == ESM::MagicEffect::DamageHealth) { applyDynamicStatsEffect(0, target, magnitude * -1); @@ -681,7 +684,7 @@ namespace MWMechanics void CastSpell::applyDynamicStatsEffect(int attribute, const MWWorld::Ptr& target, float magnitude) { DynamicStat value = target.getClass().getCreatureStats(target).getDynamic(attribute); - value.modify(magnitude); + value.setCurrent(value.getCurrent()+magnitude, attribute == 2); target.getClass().getCreatureStats(target).setDynamic(attribute, value); } @@ -766,8 +769,7 @@ namespace MWMechanics if (!mTarget.isEmpty()) { - if (!mTarget.getClass().isActor() || !mTarget.getClass().getCreatureStats(mTarget).isDead()) - inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Touch); + inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Touch); } std::string projectileModel; @@ -851,10 +853,7 @@ namespace MWMechanics if (!mTarget.isEmpty()) { - if (!mTarget.getClass().isActor() || !mTarget.getClass().getCreatureStats(mTarget).isDead()) - { - inflict(mTarget, mCaster, spell->mEffects, ESM::RT_Touch); - } + inflict(mTarget, mCaster, spell->mEffects, ESM::RT_Touch); } diff --git a/apps/openmw/mwmechanics/summoning.cpp b/apps/openmw/mwmechanics/summoning.cpp index ec9bd0ea01..668031141c 100644 --- a/apps/openmw/mwmechanics/summoning.cpp +++ b/apps/openmw/mwmechanics/summoning.cpp @@ -51,6 +51,10 @@ namespace MWMechanics } + UpdateSummonedCreatures::~UpdateSummonedCreatures() + { + } + void UpdateSummonedCreatures::visit(EffectKey key, const std::string &sourceName, const std::string &sourceId, int casterActorId, float magnitude, float remainingTime, float totalTime) { if (isSummoningEffect(key.mId) && magnitude > 0) diff --git a/apps/openmw/mwmechanics/summoning.hpp b/apps/openmw/mwmechanics/summoning.hpp index b8fe377838..8e418cdeb4 100644 --- a/apps/openmw/mwmechanics/summoning.hpp +++ b/apps/openmw/mwmechanics/summoning.hpp @@ -14,6 +14,7 @@ namespace MWMechanics struct UpdateSummonedCreatures : public EffectSourceVisitor { UpdateSummonedCreatures(const MWWorld::Ptr& actor); + virtual ~UpdateSummonedCreatures(); virtual void visit (MWMechanics::EffectKey key, const std::string& sourceName, const std::string& sourceId, int casterActorId, diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index b83a078ba7..05b43d54f5 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -120,14 +120,12 @@ RenderingManager::RenderingManager(OEngine::Render::OgreRenderer& _rend, const b // Set default texture filtering options TextureFilterOptions tfo; std::string filter = Settings::Manager::getString("texture filtering", "General"); -#ifndef ANDROID + if (filter == "anisotropic") tfo = TFO_ANISOTROPIC; else if (filter == "trilinear") tfo = TFO_TRILINEAR; else if (filter == "bilinear") tfo = TFO_BILINEAR; else /*if (filter == "none")*/ tfo = TFO_NONE; -#else - tfo = TFO_NONE; -#endif + MaterialManager::getSingleton().setDefaultTextureFiltering(tfo); MaterialManager::getSingleton().setDefaultAnisotropy( (filter == "anisotropic") ? Settings::Manager::getInt("anisotropy", "General") : 1 ); diff --git a/apps/openmw/mwrender/ripplesimulation.cpp b/apps/openmw/mwrender/ripplesimulation.cpp index 84794162f3..bb1bccc0a3 100644 --- a/apps/openmw/mwrender/ripplesimulation.cpp +++ b/apps/openmw/mwrender/ripplesimulation.cpp @@ -113,7 +113,7 @@ void RippleSimulation::update(float dt, Ogre::Vector2 position) position.z = 0; // Z is set by the Scene Node rotSpeed = mRippleRotSpeed; rotation = Ogre::Radian(Ogre::Math::RangeRandom(-Ogre::Math::PI, Ogre::Math::PI)); - created->setDimensions(50,50); + created->setDimensions(mParticleSystem->getDefaultWidth(), mParticleSystem->getDefaultHeight()); } } diff --git a/apps/openmw/mwworld/actiontrap.cpp b/apps/openmw/mwworld/actiontrap.cpp index 1472afc087..d153b7e618 100644 --- a/apps/openmw/mwworld/actiontrap.cpp +++ b/apps/openmw/mwworld/actiontrap.cpp @@ -1,16 +1,39 @@ #include "actiontrap.hpp" #include "../mwmechanics/spellcasting.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" namespace MWWorld { void ActionTrap::executeImp(const Ptr &actor) { - MWMechanics::CastSpell cast(mTrapSource, actor); - cast.mHitPosition = Ogre::Vector3(actor.getRefData().getPosition().pos); - cast.cast(mSpellId); + Ogre::Vector3 actorPosition(actor.getRefData().getPosition().pos); + Ogre::Vector3 trapPosition(mTrapSource.getRefData().getPosition().pos); + float activationDistance = MWBase::Environment::get().getWorld()->getMaxActivationDistance(); + // GUI calcs if object in activation distance include object and player geometry + const float fudgeFactor = 1.25f; + + // Hack: if actor is beyond activation range, then assume actor is using telekinesis + // to open door/container. + // Note, can't just detonate the trap at the trapped object's location and use the blast + // radius, because for most trap spells this is 1 foot, much less than the activation distance. + if (trapPosition.distance(actorPosition) < (activationDistance * fudgeFactor)) + { + // assume actor touched trap + MWMechanics::CastSpell cast(mTrapSource, actor); + cast.mHitPosition = actorPosition; + cast.cast(mSpellId); + } + else + { + // assume telekinesis used + MWMechanics::CastSpell cast(mTrapSource, mTrapSource); + cast.mHitPosition = trapPosition; + cast.cast(mSpellId); + } mTrapSource.getCellRef().setTrap(""); } diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index f6f2a3b489..d7036d6b19 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -3,6 +3,8 @@ #include #include +#include +#include #include #include "livecellref.hpp" @@ -181,7 +183,12 @@ namespace MWWorld template CellRefList& get() { - throw std::runtime_error ("Storage for this type not exist in cells"); + throw std::runtime_error ("Storage for type " + std::string(typeid(T).name())+ " does not exist in cells"); + } + + template + const CellRefList& getReadOnly() { + throw std::runtime_error ("Read Only CellRefList access not available for type " + std::string(typeid(T).name()) ); } bool isPointConnected(const int start, const int end) const; @@ -357,6 +364,12 @@ namespace MWWorld return mWeapons; } + template<> + inline const CellRefList& CellStore::getReadOnly() + { + return mDoors; + } + bool operator== (const CellStore& left, const CellStore& right); bool operator!= (const CellStore& left, const CellStore& right); } diff --git a/apps/openmw/mwworld/refdata.hpp b/apps/openmw/mwworld/refdata.hpp index da7986ba03..e90b44f9c9 100644 --- a/apps/openmw/mwworld/refdata.hpp +++ b/apps/openmw/mwworld/refdata.hpp @@ -33,11 +33,11 @@ namespace MWWorld MWScript::Locals mLocals; // if we find the overhead of heaving a locals // object in the refdata of refs without a script, // we can make this a pointer later. + bool mDeleted; // separate delete flag used for deletion by a content file bool mHasLocals; bool mEnabled; int mCount; // 0: deleted - bool mDeleted; // separate delete flag used for deletion by a content file ESM::Position mPosition; diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 72d475f169..f03f65b36e 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -230,7 +230,7 @@ namespace MWWorld cell->getCell()->getGridX(), cell->getCell()->getGridY() ); - if (land) { + if (land && land->mDataTypes&ESM::Land::DATA_VHGT) { // Actually only VHGT is needed here, but we'll need the rest for rendering anyway. // Load everything now to reduce IO overhead. const int flags = ESM::Land::DATA_VCLR|ESM::Land::DATA_VHGT|ESM::Land::DATA_VNML|ESM::Land::DATA_VTEX; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 188f3cdad3..8a2b67bfcb 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -423,6 +423,19 @@ namespace MWWorld globals["werewolfclawmult"] = ESM::Variant(25.f); globals["pcknownwerewolf"] = ESM::Variant(0); + // following should exist in all versions of MW, but not necessarily in TCs + globals["gamehour"] = ESM::Variant(0.f); + globals["timescale"] = ESM::Variant(30.f); + globals["day"] = ESM::Variant(1); + globals["month"] = ESM::Variant(1); + globals["year"] = ESM::Variant(1); + globals["pcrace"] = ESM::Variant(0); + globals["pchascrimegold"] = ESM::Variant(0); + globals["pchasgolddiscount"] = ESM::Variant(0); + globals["crimegolddiscount"] = ESM::Variant(0); + globals["crimegoldturnin"] = ESM::Variant(0); + globals["pchasturnin"] = ESM::Variant(0); + for (std::map::iterator it = gmst.begin(); it != gmst.end(); ++it) { if (!mStore.get().search(it->first)) @@ -2784,18 +2797,45 @@ namespace MWWorld { if (cell->isExterior()) return false; - MWWorld::CellRefList& doors = cell->get(); - CellRefList::List& refList = doors.mList; - // Check if any door in the cell leads to an exterior directly - for (CellRefList::List::iterator it = refList.begin(); it != refList.end(); ++it) - { - MWWorld::LiveCellRef& ref = *it; - if (ref.mRef.getTeleport() && ref.mRef.getDestCell().empty()) - { - ESM::Position pos = ref.mRef.getDoorDest(); - result = Ogre::Vector3(pos.pos); - return true; + // Search for a 'nearest' exterior, counting each cell between the starting + // cell and the exterior as a distance of 1. Will fail for isolated interiors. + std::set< std::string >checkedCells; + std::set< std::string >currentCells; + std::set< std::string >nextCells; + nextCells.insert( cell->getCell()->mName ); + + while ( !nextCells.empty() ) { + currentCells = nextCells; + nextCells.clear(); + for( std::set< std::string >::const_iterator i = currentCells.begin(); i != currentCells.end(); ++i ) { + MWWorld::CellStore *next = getInterior( *i ); + if ( !next ) continue; + + const MWWorld::CellRefList& doors = next->getReadOnly(); + const CellRefList::List& refList = doors.mList; + + // Check if any door in the cell leads to an exterior directly + for (CellRefList::List::const_iterator it = refList.begin(); it != refList.end(); ++it) + { + const MWWorld::LiveCellRef& ref = *it; + if (!ref.mRef.getTeleport()) continue; + + if (ref.mRef.getDestCell().empty()) + { + ESM::Position pos = ref.mRef.getDoorDest(); + result = Ogre::Vector3(pos.pos); + return true; + } + else + { + std::string dest = ref.mRef.getDestCell(); + if ( !checkedCells.count(dest) && !currentCells.count(dest) ) + nextCells.insert(dest); + } + } + + checkedCells.insert( *i ); } } @@ -2803,33 +2843,102 @@ namespace MWWorld return false; } - void World::teleportToClosestMarker (const MWWorld::Ptr& ptr, - const std::string& id) + MWWorld::Ptr World::getClosestMarker( const MWWorld::Ptr &ptr, const std::string &id ) { - Ogre::Vector3 worldPos; - if (!findInteriorPositionInWorldSpace(ptr.getCell(), worldPos)) - worldPos = mPlayer->getLastKnownExteriorPosition(); + if ( ptr.getCell()->isExterior() ) { + return getClosestMarkerFromExteriorPosition(mPlayer->getLastKnownExteriorPosition(), id); + } + // Search for a 'nearest' marker, counting each cell between the starting + // cell and the exterior as a distance of 1. If an exterior is found, jump + // to the nearest exterior marker, without further interior searching. + std::set< std::string >checkedCells; + std::set< std::string >currentCells; + std::set< std::string >nextCells; + MWWorld::Ptr closestMarker; + + nextCells.insert( ptr.getCell()->getCell()->mName ); + while ( !nextCells.empty() ) { + currentCells = nextCells; + nextCells.clear(); + for( std::set< std::string >::const_iterator i = currentCells.begin(); i != currentCells.end(); ++i ) { + MWWorld::CellStore *next = getInterior( *i ); + checkedCells.insert( *i ); + if ( !next ) continue; + + closestMarker = next->search( id ); + if ( !closestMarker.isEmpty() ) + { + return closestMarker; + } + + const MWWorld::CellRefList& doors = next->getReadOnly(); + const CellRefList::List& doorList = doors.mList; + + // Check if any door in the cell leads to an exterior directly + for (CellRefList::List::const_iterator it = doorList.begin(); it != doorList.end(); ++it) + { + const MWWorld::LiveCellRef& ref = *it; + + if (!ref.mRef.getTeleport()) continue; + + if (ref.mRef.getDestCell().empty()) + { + Ogre::Vector3 worldPos = Ogre::Vector3(ref.mRef.getDoorDest().pos); + return getClosestMarkerFromExteriorPosition(worldPos, id); + } + else + { + std::string dest = ref.mRef.getDestCell(); + if ( !checkedCells.count(dest) && !currentCells.count(dest) ) + nextCells.insert(dest); + } + } + } + } + + return MWWorld::Ptr(); + } + + MWWorld::Ptr World::getClosestMarkerFromExteriorPosition( const Ogre::Vector3 worldPos, const std::string &id ) { MWWorld::Ptr closestMarker; float closestDistance = FLT_MAX; std::vector markers; mCells.getExteriorPtrs(id, markers); - - for (std::vector::iterator it = markers.begin(); it != markers.end(); ++it) + for (std::vector::iterator it2 = markers.begin(); it2 != markers.end(); ++it2) { - ESM::Position pos = it->getRefData().getPosition(); + ESM::Position pos = it2->getRefData().getPosition(); Ogre::Vector3 markerPos = Ogre::Vector3(pos.pos); float distance = worldPos.squaredDistance(markerPos); if (distance < closestDistance) { closestDistance = distance; - closestMarker = *it; + closestMarker = *it2; } } - MWWorld::ActionTeleport action("", closestMarker.getRefData().getPosition()); + return closestMarker; + } + + + void World::teleportToClosestMarker (const MWWorld::Ptr& ptr, + const std::string& id) + { + MWWorld::Ptr closestMarker = getClosestMarker( ptr, id ); + + if ( closestMarker.isEmpty() ) + { + std::cerr << "Failed to teleport: no closest marker found" << std::endl; + return; + } + + std::string cellName; + if ( !closestMarker.mCell->isExterior() ) + cellName = closestMarker.mCell->getCell()->mName; + + MWWorld::ActionTeleport action(cellName, closestMarker.getRefData().getPosition()); action.execute(ptr); } @@ -2978,31 +3087,26 @@ namespace MWWorld void World::confiscateStolenItems(const Ptr &ptr) { - Ogre::Vector3 playerPos; - if (!findInteriorPositionInWorldSpace(ptr.getCell(), playerPos)) - playerPos = mPlayer->getLastKnownExteriorPosition(); - - MWWorld::Ptr closestChest; - float closestDistance = FLT_MAX; - - //Find closest stolen_goods chest - std::vector chests; - mCells.getInteriorPtrs("stolen_goods", chests); - - Ogre::Vector3 chestPos; - for (std::vector::iterator it = chests.begin(); it != chests.end(); ++it) + MWWorld::Ptr prisonMarker = getClosestMarker( ptr, "prisonmarker" ); + if ( prisonMarker.isEmpty() ) { - if (!findInteriorPositionInWorldSpace(it->getCell(), chestPos)) - continue; - - float distance = playerPos.squaredDistance(chestPos); - if (distance < closestDistance) - { - closestDistance = distance; - closestChest = *it; - } + std::cerr << "Failed to confiscate items: no closest prison marker found." << std::endl; + return; + } + std::string prisonName = prisonMarker.mRef->mRef.getDestCell(); + if ( prisonName.empty() ) + { + std::cerr << "Failed to confiscate items: prison marker not linked to prison interior" << std::endl; + return; + } + MWWorld::CellStore *prison = getInterior( prisonName ); + if ( !prison ) + { + std::cerr << "Failed to confiscate items: failed to load cell " << prisonName << std::endl; + return; } + MWWorld::Ptr closestChest = prison->search( "stolen_goods" ); if (!closestChest.isEmpty()) //Found a close chest { MWBase::Environment::get().getMechanicsManager()->confiscateStolenItems(ptr, closestChest); @@ -3108,7 +3212,7 @@ namespace MWWorld mRendering->spawnEffect(model, textureOverride, worldPos); } - void World::explodeSpell(const Vector3 &origin, const ESM::EffectList &effects, const Ptr &caster, int rangeType, + void World::explodeSpell(const Vector3 &origin, const ESM::EffectList &effects, const Ptr &caster, ESM::RangeType rangeType, const std::string& id, const std::string& sourceName) { std::map > toApply; @@ -3167,7 +3271,7 @@ namespace MWWorld cast.mStack = false; ESM::EffectList effects; effects.mList = apply->second; - cast.inflict(apply->first, caster, effects, (ESM::RangeType)rangeType, false, true); + cast.inflict(apply->first, caster, effects, rangeType, false, true); } } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 9980f6f906..1db86f7383 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -115,7 +115,6 @@ namespace MWWorld void performUpdateSceneQueries (); void getFacedHandle(std::string& facedHandle, float maxDistance, bool ignorePlayer=true); - float getMaxActivationDistance (); float getNpcActivationDistance (); float getObjectActivationDistance (); @@ -151,6 +150,9 @@ namespace MWWorld float feetToGameUnits(float feet); + MWWorld::Ptr getClosestMarker( const MWWorld::Ptr &ptr, const std::string &id ); + MWWorld::Ptr getClosestMarkerFromExteriorPosition( const Ogre::Vector3 worldPos, const std::string &id ); + public: World (OEngine::Render::OgreRenderer& renderer, @@ -363,6 +365,8 @@ namespace MWWorld virtual MWWorld::Ptr safePlaceObject(const MWWorld::Ptr& ptr, MWWorld::CellStore* cell, ESM::Position pos); ///< place an object in a "safe" location (ie not in the void, etc). Makes a copy of the Ptr. + virtual float getMaxActivationDistance(); + virtual void indexToPosition (int cellX, int cellY, float &x, float &y, bool centre = false) const; ///< Convert cell numbers to position. @@ -633,7 +637,7 @@ namespace MWWorld virtual void spawnEffect (const std::string& model, const std::string& textureOverride, const Ogre::Vector3& worldPos); virtual void explodeSpell (const Ogre::Vector3& origin, const ESM::EffectList& effects, - const MWWorld::Ptr& caster, int rangeType, const std::string& id, const std::string& sourceName); + const MWWorld::Ptr& caster, ESM::RangeType rangeType, const std::string& id, const std::string& sourceName); virtual void activate (const MWWorld::Ptr& object, const MWWorld::Ptr& actor); diff --git a/components/config/launchersettings.cpp b/components/config/launchersettings.cpp index 66f05f691c..1d4b428c91 100644 --- a/components/config/launchersettings.cpp +++ b/components/config/launchersettings.cpp @@ -105,6 +105,12 @@ void Config::LauncherSettings::setContentList(const GameSettings& gameSettings) // obtain content list from game settings (if present) const QStringList files(gameSettings.getContentList()); + // if openmw.cfg has no content, exit so we don't create an empty content list. + if (files.isEmpty()) + { + return; + } + // if any existing profile in launcher matches the content list, make that profile the default foreach(const QString &listName, getContentLists()) { diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index ec1fcc21e4..7850b2cd60 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -110,15 +110,15 @@ Qt::ItemFlags ContentSelectorModel::ContentModel::flags(const QModelIndex &index if (!file) return Qt::NoItemFlags; - //game files can always be checked + //game files are not shown if (file->isGameFile()) - return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable; + return Qt::NoItemFlags; Qt::ItemFlags returnFlags; - bool allDependenciesFound = true; - bool gamefileChecked = false; - //addon can be checked if its gamefile is and all other dependencies exist + // addon can be checked if its gamefile is + // ... special case, addon with no dependency can be used with any gamefile. + bool gamefileChecked = (file->gameFiles().count() == 0); foreach (const QString &fileName, file->gameFiles()) { bool depFound = false; @@ -144,16 +144,11 @@ Qt::ItemFlags ContentSelectorModel::ContentModel::flags(const QModelIndex &index if (gamefileChecked || !(dependency->isGameFile())) break; } - - allDependenciesFound = allDependenciesFound && depFound; } if (gamefileChecked) { - if (allDependenciesFound) - returnFlags = returnFlags | Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled; - else - returnFlags = Qt::ItemIsSelectable; + returnFlags = Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled; } return returnFlags; @@ -467,6 +462,14 @@ void ContentSelectorModel::ContentModel::addFiles(const QString &path) file->setFilePath (info.absoluteFilePath()); file->setDescription(QString::fromUtf8(fileReader.getDesc().c_str())); + // HACK + // Load order constraint of Bloodmoon.esm needing Tribunal.esm is missing + // from the file supplied by Bethesda, so we have to add it ourselves + if (file->fileName().compare("Bloodmoon.esm", Qt::CaseInsensitive) == 0) + { + file->addGameFile(QString::fromUtf8("Tribunal.esm")); + } + // Put the file in the table addFile(file); @@ -481,6 +484,19 @@ void ContentSelectorModel::ContentModel::addFiles(const QString &path) sortFiles(); } +QStringList ContentSelectorModel::ContentModel::gameFiles() const +{ + QStringList gameFiles; + foreach(const ContentSelectorModel::EsmFile *file, mFiles) + { + if (file->isGameFile()) + { + gameFiles.append(file->fileName()); + } + } + return gameFiles; +} + void ContentSelectorModel::ContentModel::sortFiles() { //first, sort the model such that all dependencies are ordered upstream (gamefile) first. @@ -536,13 +552,13 @@ bool ContentSelectorModel::ContentModel::isLoadOrderError(const EsmFile *file) c return mPluginsWithLoadOrderError.contains(file->filePath()); } -void ContentSelectorModel::ContentModel::setContentList(const QStringList &fileList, bool isChecked) +void ContentSelectorModel::ContentModel::setContentList(const QStringList &fileList) { mPluginsWithLoadOrderError.clear(); int previousPosition = -1; foreach (const QString &filepath, fileList) { - if (setCheckState(filepath, isChecked)) + if (setCheckState(filepath, true)) { // as necessary, move plug-ins in visible list to match sequence of supplied filelist const EsmFile* file = item(filepath); @@ -581,7 +597,7 @@ void ContentSelectorModel::ContentModel::checkForLoadOrderErrors() QList ContentSelectorModel::ContentModel::checkForLoadOrderErrors(const EsmFile *file, int row) const { QList errors = QList(); - foreach(QString dependentfileName, file->gameFiles()) + foreach(const QString &dependentfileName, file->gameFiles()) { const EsmFile* dependentFile = item(dependentfileName); diff --git a/components/contentselector/model/contentmodel.hpp b/components/contentselector/model/contentmodel.hpp index 6af5425c77..6585558520 100644 --- a/components/contentselector/model/contentmodel.hpp +++ b/components/contentselector/model/contentmodel.hpp @@ -47,11 +47,12 @@ namespace ContentSelectorModel QModelIndex indexFromItem(const EsmFile *item) const; const EsmFile *item(const QString &name) const; + QStringList gameFiles() const; bool isEnabled (QModelIndex index) const; bool isChecked(const QString &filepath) const; bool setCheckState(const QString &filepath, bool isChecked); - void setContentList(const QStringList &fileList, bool isChecked); + void setContentList(const QStringList &fileList); ContentFileList checkedItems() const; void uncheckAll(); diff --git a/components/contentselector/model/esmfile.cpp b/components/contentselector/model/esmfile.cpp index a0a09105a7..1ac1c75008 100644 --- a/components/contentselector/model/esmfile.cpp +++ b/components/contentselector/model/esmfile.cpp @@ -62,6 +62,13 @@ QByteArray ContentSelectorModel::EsmFile::encodedData() const return encodedData; } +bool ContentSelectorModel::EsmFile::isGameFile() const +{ + return (mGameFiles.size() == 0) && + (mFileName.endsWith(QLatin1String(".esm"), Qt::CaseInsensitive) || + mFileName.endsWith(QLatin1String(".omwgame"), Qt::CaseInsensitive)); +} + QVariant ContentSelectorModel::EsmFile::fileProperty(const FileProperty prop) const { switch (prop) diff --git a/components/contentselector/model/esmfile.hpp b/components/contentselector/model/esmfile.hpp index 7e1edcaba6..d0cebab3c6 100644 --- a/components/contentselector/model/esmfile.hpp +++ b/components/contentselector/model/esmfile.hpp @@ -64,7 +64,7 @@ namespace ContentSelectorModel .arg(mGameFiles.join(", ")); } - inline bool isGameFile() const { return (mGameFiles.size() == 0); } + bool isGameFile() const; QByteArray encodedData() const; public: diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index ad023e5b22..2363ae477a 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include ContentSelectorView::ContentSelector::ContentSelector(QWidget *parent) : @@ -33,13 +34,7 @@ void ContentSelectorView::ContentSelector::buildGameFileView() { ui.gameFileView->setVisible (true); - mGameFileProxyModel = new QSortFilterProxyModel(this); - mGameFileProxyModel->setFilterRegExp(QString::number((int)ContentSelectorModel::ContentType_GameFile)); - mGameFileProxyModel->setFilterRole (Qt::UserRole); - mGameFileProxyModel->setSourceModel (mContentModel); - ui.gameFileView->setPlaceholderText(QString("Select a game file...")); - ui.gameFileView->setModel(mGameFileProxyModel); connect (ui.gameFileView, SIGNAL (currentIndexChanged(int)), this, SLOT (slotCurrentGameFileIndexChanged(int))); @@ -113,7 +108,7 @@ void ContentSelectorView::ContentSelector::setContentList(const QStringList &lis slotCurrentGameFileIndexChanged (ui.gameFileView->currentIndex()); } else - mContentModel->setContentList(list, true); + mContentModel->setContentList(list); } ContentSelectorModel::ContentFileList @@ -129,6 +124,15 @@ void ContentSelectorView::ContentSelector::addFiles(const QString &path) { mContentModel->addFiles(path); + // add any game files to the combo box + foreach(const QString gameFileName, mContentModel->gameFiles()) + { + if (ui.gameFileView->findText(gameFileName) == -1) + { + ui.gameFileView->addItem(gameFileName); + } + } + if (ui.gameFileView->currentIndex() != -1) ui.gameFileView->setCurrentIndex(-1); @@ -150,29 +154,33 @@ void ContentSelectorView::ContentSelector::slotCurrentGameFileIndexChanged(int i { static int oldIndex = -1; - QAbstractItemModel *const model = ui.gameFileView->model(); - QSortFilterProxyModel *proxy = dynamic_cast(model); - - if (proxy) - proxy->setDynamicSortFilter(false); - if (index != oldIndex) { if (oldIndex > -1) - model->setData(model->index(oldIndex, 0), false, Qt::UserRole + 1); + { + setGameFileSelected(oldIndex, false); + } oldIndex = index; - model->setData(model->index(index, 0), true, Qt::UserRole + 1); + setGameFileSelected(index, true); mContentModel->checkForLoadOrderErrors(); } - if (proxy) - proxy->setDynamicSortFilter(true); - emit signalCurrentGamefileIndexChanged (index); } +void ContentSelectorView::ContentSelector::setGameFileSelected(int index, bool selected) +{ + QString fileName = ui.gameFileView->itemText(index); + const ContentSelectorModel::EsmFile* file = mContentModel->item(fileName); + if (file != NULL) + { + QModelIndex index(mContentModel->indexFromItem(file)); + mContentModel->setData(index, selected, Qt::UserRole + 1); + } +} + void ContentSelectorView::ContentSelector::slotAddonTableItemActivated(const QModelIndex &index) { QModelIndex sourceIndex = mAddonProxyModel->mapToSource (index); diff --git a/components/contentselector/view/contentselector.hpp b/components/contentselector/view/contentselector.hpp index 8651dac2ef..2507cf6adb 100644 --- a/components/contentselector/view/contentselector.hpp +++ b/components/contentselector/view/contentselector.hpp @@ -19,7 +19,6 @@ namespace ContentSelectorView protected: ContentSelectorModel::ContentModel *mContentModel; - QSortFilterProxyModel *mGameFileProxyModel; QSortFilterProxyModel *mAddonProxyModel; public: @@ -52,6 +51,7 @@ namespace ContentSelectorView void buildContentModel(); void buildGameFileView(); void buildAddonView(); + void setGameFileSelected(int index, bool selected); signals: void signalCurrentGamefileIndexChanged (int); diff --git a/components/esm/aipackage.cpp b/components/esm/aipackage.cpp index 8ad64b7975..efcd6651ea 100644 --- a/components/esm/aipackage.cpp +++ b/components/esm/aipackage.cpp @@ -41,29 +41,6 @@ namespace ESM } - void AIPackageList::load(ESMReader &esm) - { - mList.clear(); - while (esm.hasMoreSubs()) { - // initialize every iteration - esm.getSubName(); - switch (esm.retSubName().val) - { - case AI_Wander: - case AI_Activate: - case AI_Escort: - case AI_Follow: - case AI_Travel: - case AI_CNDT: - - add(esm); - break; - default: - return; - } - } - } - void AIPackageList::save(ESMWriter &esm) const { typedef std::vector::const_iterator PackageIter; diff --git a/components/esm/aipackage.hpp b/components/esm/aipackage.hpp index 30bd2ce04c..5e08806c8b 100644 --- a/components/esm/aipackage.hpp +++ b/components/esm/aipackage.hpp @@ -94,9 +94,6 @@ namespace ESM /// Add a single AIPackage, assumes subrecord name was already read void add(ESMReader &esm); - /// TODO: remove this method. The ESM format does not guarantee that all AI packages follow one another - void load(ESMReader &esm); - void save(ESMWriter &esm) const; }; } diff --git a/components/esm/effectlist.cpp b/components/esm/effectlist.cpp index fc9acccf20..f6d5a6e071 100644 --- a/components/esm/effectlist.cpp +++ b/components/esm/effectlist.cpp @@ -8,13 +8,18 @@ namespace ESM { void EffectList::load(ESMReader &esm) { mList.clear(); - ENAMstruct s; while (esm.isNextSub("ENAM")) { - esm.getHT(s, 24); - mList.push_back(s); + add(esm); } } +void EffectList::add(ESMReader &esm) +{ + ENAMstruct s; + esm.getHT(s, 24); + mList.push_back(s); +} + void EffectList::save(ESMWriter &esm) const { for (std::vector::const_iterator it = mList.begin(); it != mList.end(); ++it) { diff --git a/components/esm/effectlist.hpp b/components/esm/effectlist.hpp index 04adcc5cd8..d581f83371 100644 --- a/components/esm/effectlist.hpp +++ b/components/esm/effectlist.hpp @@ -29,11 +29,15 @@ namespace ESM }; #pragma pack(pop) + /// EffectList, ENAM subrecord struct EffectList { - std::vector mList; + /// Load one effect, assumes subrecord name was already read + void add(ESMReader &esm); + + /// Load all effects void load(ESMReader &esm); void save(ESMWriter &esm) const; }; diff --git a/components/esm/loadacti.cpp b/components/esm/loadacti.cpp index 8efea3302a..b5adce5509 100644 --- a/components/esm/loadacti.cpp +++ b/components/esm/loadacti.cpp @@ -8,18 +8,34 @@ namespace ESM { unsigned int Activator::sRecordId = REC_ACTI; -void Activator::load(ESMReader &esm) -{ - mModel = esm.getHNString("MODL"); - mName = esm.getHNOString("FNAM"); - mScript = esm.getHNOString("SCRI"); -} -void Activator::save(ESMWriter &esm) const -{ - esm.writeHNCString("MODL", mModel); - esm.writeHNOCString("FNAM", mName); - esm.writeHNOCString("SCRI", mScript); -} + void Activator::load(ESMReader &esm) + { + while (esm.hasMoreSubs()) + { + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::FourCC<'M','O','D','L'>::value: + mModel = esm.getHString(); + break; + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'S','C','R','I'>::value: + mScript = esm.getHString(); + break; + default: + esm.fail("Unknown subrecord"); + } + } + } + void Activator::save(ESMWriter &esm) const + { + esm.writeHNCString("MODL", mModel); + esm.writeHNOCString("FNAM", mName); + esm.writeHNOCString("SCRI", mScript); + } void Activator::blank() { diff --git a/components/esm/loadalch.cpp b/components/esm/loadalch.cpp index aac88482ff..18db512c0c 100644 --- a/components/esm/loadalch.cpp +++ b/components/esm/loadalch.cpp @@ -8,24 +8,51 @@ namespace ESM { unsigned int Potion::sRecordId = REC_ALCH; -void Potion::load(ESMReader &esm) -{ - mModel = esm.getHNOString("MODL"); - mIcon = esm.getHNOString("TEXT"); // not ITEX here for some reason - mScript = esm.getHNOString("SCRI"); - mName = esm.getHNOString("FNAM"); - esm.getHNT(mData, "ALDT", 12); - mEffects.load(esm); -} -void Potion::save(ESMWriter &esm) const -{ - esm.writeHNCString("MODL", mModel); - esm.writeHNOCString("TEXT", mIcon); - esm.writeHNOCString("SCRI", mScript); - esm.writeHNOCString("FNAM", mName); - esm.writeHNT("ALDT", mData, 12); - mEffects.save(esm); -} + void Potion::load(ESMReader &esm) + { + mEffects.mList.clear(); + bool hasData = false; + while (esm.hasMoreSubs()) + { + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::FourCC<'M','O','D','L'>::value: + mModel = esm.getHString(); + break; + case ESM::FourCC<'T','E','X','T'>::value: // not ITEX here for some reason + mIcon = esm.getHString(); + break; + case ESM::FourCC<'S','C','R','I'>::value: + mScript = esm.getHString(); + break; + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'A','L','D','T'>::value: + esm.getHT(mData, 12); + hasData = true; + break; + case ESM::FourCC<'E','N','A','M'>::value: + mEffects.add(esm); + break; + default: + esm.fail("Unknown subrecord"); + } + } + if (!hasData) + esm.fail("Missing ALDT"); + } + void Potion::save(ESMWriter &esm) const + { + esm.writeHNCString("MODL", mModel); + esm.writeHNOCString("TEXT", mIcon); + esm.writeHNOCString("SCRI", mScript); + esm.writeHNOCString("FNAM", mName); + esm.writeHNT("ALDT", mData, 12); + mEffects.save(esm); + } void Potion::blank() { diff --git a/components/esm/loadappa.cpp b/components/esm/loadappa.cpp index 29ea78acc0..f2c82aacfb 100644 --- a/components/esm/loadappa.cpp +++ b/components/esm/loadappa.cpp @@ -10,25 +10,35 @@ namespace ESM void Apparatus::load(ESMReader &esm) { - // we will not treat duplicated subrecords as errors here + bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - NAME subName = esm.retSubName(); - - if (subName == "MODL") - mModel = esm.getHString(); - else if (subName == "FNAM") - mName = esm.getHString(); - else if (subName == "AADT") - esm.getHT(mData); - else if (subName == "SCRI") - mScript = esm.getHString(); - else if (subName == "ITEX") - mIcon = esm.getHString(); - else - esm.fail("wrong subrecord type " + subName.toString() + " for APPA record"); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::FourCC<'M','O','D','L'>::value: + mModel = esm.getHString(); + break; + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'A','A','D','T'>::value: + esm.getHT(mData); + hasData = true; + break; + case ESM::FourCC<'S','C','R','I'>::value: + mScript = esm.getHString(); + break; + case ESM::FourCC<'I','T','E','X'>::value: + mIcon = esm.getHString(); + break; + default: + esm.fail("Unknown subrecord"); + } } + if (!hasData) + esm.fail("Missing AADT"); } void Apparatus::save(ESMWriter &esm) const diff --git a/components/esm/loadbody.cpp b/components/esm/loadbody.cpp index 9a1164d041..ed24ded57b 100644 --- a/components/esm/loadbody.cpp +++ b/components/esm/loadbody.cpp @@ -11,9 +11,30 @@ namespace ESM void BodyPart::load(ESMReader &esm) { - mModel = esm.getHNString("MODL"); - mRace = esm.getHNOString("FNAM"); - esm.getHNT(mData, "BYDT", 4); + bool hasData = false; + while (esm.hasMoreSubs()) + { + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::FourCC<'M','O','D','L'>::value: + mModel = esm.getHString(); + break; + case ESM::FourCC<'F','N','A','M'>::value: + mRace = esm.getHString(); + break; + case ESM::FourCC<'B','Y','D','T'>::value: + esm.getHT(mData, 4); + hasData = true; + break; + default: + esm.fail("Unknown subrecord"); + } + } + + if (!hasData) + esm.fail("Missing BYDT subrecord"); } void BodyPart::save(ESMWriter &esm) const { diff --git a/components/esm/loadbsgn.cpp b/components/esm/loadbsgn.cpp index db1a72a368..e0cd83ea07 100644 --- a/components/esm/loadbsgn.cpp +++ b/components/esm/loadbsgn.cpp @@ -10,11 +10,29 @@ namespace ESM void BirthSign::load(ESMReader &esm) { - mName = esm.getHNOString("FNAM"); - mTexture = esm.getHNOString("TNAM"); - mDescription = esm.getHNOString("DESC"); - - mPowers.load(esm); + mPowers.mList.clear(); + while (esm.hasMoreSubs()) + { + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'T','N','A','M'>::value: + mTexture = esm.getHString(); + break; + case ESM::FourCC<'D','E','S','C'>::value: + mDescription = esm.getHString(); + break; + case ESM::FourCC<'N','P','C','S'>::value: + mPowers.add(esm); + break; + default: + esm.fail("Unknown subrecord"); + } + } } void BirthSign::save(ESMWriter &esm) const diff --git a/components/esm/loadclas.cpp b/components/esm/loadclas.cpp index ec339bd15e..66acaea721 100644 --- a/components/esm/loadclas.cpp +++ b/components/esm/loadclas.cpp @@ -10,17 +10,17 @@ namespace ESM { unsigned int Class::sRecordId = REC_CLAS; -const Class::Specialization Class::sSpecializationIds[3] = { - Class::Combat, - Class::Magic, - Class::Stealth -}; + const Class::Specialization Class::sSpecializationIds[3] = { + Class::Combat, + Class::Magic, + Class::Stealth + }; -const char *Class::sGmstSpecializationIds[3] = { - "sSpecializationCombat", - "sSpecializationMagic", - "sSpecializationStealth" -}; + const char *Class::sGmstSpecializationIds[3] = { + "sSpecializationCombat", + "sSpecializationMagic", + "sSpecializationStealth" + }; int& Class::CLDTstruct::getSkill (int index, bool major) @@ -39,22 +39,40 @@ const char *Class::sGmstSpecializationIds[3] = { return mSkills[index][major ? 1 : 0]; } -void Class::load(ESMReader &esm) -{ - mName = esm.getHNOString("FNAM"); - esm.getHNT(mData, "CLDT", 60); - - if (mData.mIsPlayable > 1) - esm.fail("Unknown bool value"); - - mDescription = esm.getHNOString("DESC"); -} -void Class::save(ESMWriter &esm) const -{ - esm.writeHNOCString("FNAM", mName); - esm.writeHNT("CLDT", mData, 60); - esm.writeHNOString("DESC", mDescription); -} + void Class::load(ESMReader &esm) + { + bool hasData = false; + while (esm.hasMoreSubs()) + { + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'C','L','D','T'>::value: + esm.getHT(mData, 60); + if (mData.mIsPlayable > 1) + esm.fail("Unknown bool value"); + hasData = true; + break; + case ESM::FourCC<'D','E','S','C'>::value: + mDescription = esm.getHString(); + break; + default: + esm.fail("Unknown subrecord"); + } + } + if (!hasData) + esm.fail("Missing CLDT subrecord"); + } + void Class::save(ESMWriter &esm) const + { + esm.writeHNOCString("FNAM", mName); + esm.writeHNT("CLDT", mData, 60); + esm.writeHNOString("DESC", mDescription); + } void Class::blank() { diff --git a/components/esm/loadcont.cpp b/components/esm/loadcont.cpp index f48f069306..999c3f92a1 100644 --- a/components/esm/loadcont.cpp +++ b/components/esm/loadcont.cpp @@ -7,60 +7,79 @@ namespace ESM { -void InventoryList::add(ESMReader &esm) -{ - ContItem ci; - esm.getHT(ci, 36); - mList.push_back(ci); -} - -void InventoryList::load(ESMReader &esm) -{ - mList.clear(); - while (esm.isNextSub("NPCO")) + void InventoryList::add(ESMReader &esm) { - add(esm); + ContItem ci; + esm.getHT(ci, 36); + mList.push_back(ci); } -} -void InventoryList::save(ESMWriter &esm) const -{ - for (std::vector::const_iterator it = mList.begin(); it != mList.end(); ++it) + void InventoryList::save(ESMWriter &esm) const { - esm.writeHNT("NPCO", *it, 36); + for (std::vector::const_iterator it = mList.begin(); it != mList.end(); ++it) + { + esm.writeHNT("NPCO", *it, 36); + } } -} unsigned int Container::sRecordId = REC_CONT; -void Container::load(ESMReader &esm) -{ - mModel = esm.getHNString("MODL"); - mName = esm.getHNOString("FNAM"); - esm.getHNT(mWeight, "CNDT", 4); - esm.getHNT(mFlags, "FLAG", 4); + void Container::load(ESMReader &esm) + { + mInventory.mList.clear(); + bool hasWeight = false; + bool hasFlags = false; + while (esm.hasMoreSubs()) + { + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::FourCC<'M','O','D','L'>::value: + mModel = esm.getHString(); + break; + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'C','N','D','T'>::value: + esm.getHT(mWeight, 4); + hasWeight = true; + break; + case ESM::FourCC<'F','L','A','G'>::value: + esm.getHT(mFlags, 4); + if (mFlags & 0xf4) + esm.fail("Unknown flags"); + if (!(mFlags & 0x8)) + esm.fail("Flag 8 not set"); + hasFlags = true; + break; + case ESM::FourCC<'S','C','R','I'>::value: + mScript = esm.getHString(); + break; + case ESM::FourCC<'N','P','C','O'>::value: + mInventory.add(esm); + break; + default: + esm.fail("Unknown subrecord"); + } + } + if (!hasWeight) + esm.fail("Missing CNDT subrecord"); + if (!hasFlags) + esm.fail("Missing FLAG subrecord"); + } - if (mFlags & 0xf4) - esm.fail("Unknown flags"); - if (!(mFlags & 0x8)) - esm.fail("Flag 8 not set"); + void Container::save(ESMWriter &esm) const + { + esm.writeHNCString("MODL", mModel); + esm.writeHNOCString("FNAM", mName); + esm.writeHNT("CNDT", mWeight, 4); + esm.writeHNT("FLAG", mFlags, 4); - mScript = esm.getHNOString("SCRI"); + esm.writeHNOCString("SCRI", mScript); - mInventory.load(esm); -} - -void Container::save(ESMWriter &esm) const -{ - esm.writeHNCString("MODL", mModel); - esm.writeHNOCString("FNAM", mName); - esm.writeHNT("CNDT", mWeight, 4); - esm.writeHNT("FLAG", mFlags, 4); - - esm.writeHNOCString("SCRI", mScript); - - mInventory.save(esm); -} + mInventory.save(esm); + } void Container::blank() { diff --git a/components/esm/loadcont.hpp b/components/esm/loadcont.hpp index 93db94759d..76c522d748 100644 --- a/components/esm/loadcont.hpp +++ b/components/esm/loadcont.hpp @@ -22,6 +22,7 @@ struct ContItem NAME32 mItem; }; +/// InventoryList, NPCO subrecord struct InventoryList { std::vector mList; @@ -29,8 +30,6 @@ struct InventoryList /// Load one item, assumes subrecord name is already read void add(ESMReader &esm); - /// TODO: remove this method, the ESM format doesn't guarantee that all ContItems follow one another - void load(ESMReader &esm); void save(ESMWriter &esm) const; }; diff --git a/components/esm/loadcrea.cpp b/components/esm/loadcrea.cpp index 650de08011..2e9f924b7c 100644 --- a/components/esm/loadcrea.cpp +++ b/components/esm/loadcrea.cpp @@ -8,55 +8,94 @@ namespace ESM { unsigned int Creature::sRecordId = REC_CREA; -void Creature::load(ESMReader &esm) -{ - mPersistent = esm.getRecordFlags() & 0x0400; - - mModel = esm.getHNString("MODL"); - mOriginal = esm.getHNOString("CNAM"); - mName = esm.getHNOString("FNAM"); - mScript = esm.getHNOString("SCRI"); - - esm.getHNT(mData, "NPDT", 96); - - esm.getHNT(mFlags, "FLAG"); - mScale = 1.0; - esm.getHNOT(mScale, "XSCL"); - - mInventory.load(esm); - mSpells.load(esm); - - if (esm.isNextSub("AIDT")) + void Creature::load(ESMReader &esm) { - esm.getHExact(&mAiData, sizeof(mAiData)); - mHasAI = true; - } - else + mPersistent = esm.getRecordFlags() & 0x0400; + + mAiPackage.mList.clear(); + mInventory.mList.clear(); + mSpells.mList.clear(); + + mScale = 1.f; mHasAI = false; - - mAiPackage.load(esm); - esm.skipRecord(); -} - -void Creature::save(ESMWriter &esm) const -{ - esm.writeHNCString("MODL", mModel); - esm.writeHNOCString("CNAM", mOriginal); - esm.writeHNOCString("FNAM", mName); - esm.writeHNOCString("SCRI", mScript); - esm.writeHNT("NPDT", mData, 96); - esm.writeHNT("FLAG", mFlags); - if (mScale != 1.0) { - esm.writeHNT("XSCL", mScale); + bool hasNpdt = false; + bool hasFlags = false; + while (esm.hasMoreSubs()) + { + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::FourCC<'M','O','D','L'>::value: + mModel = esm.getHString(); + break; + case ESM::FourCC<'C','N','A','M'>::value: + mOriginal = esm.getHString(); + break; + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'S','C','R','I'>::value: + mScript = esm.getHString(); + break; + case ESM::FourCC<'N','P','D','T'>::value: + esm.getHT(mData, 96); + hasNpdt = true; + break; + case ESM::FourCC<'F','L','A','G'>::value: + esm.getHT(mFlags); + hasFlags = true; + break; + case ESM::FourCC<'X','S','C','L'>::value: + esm.getHT(mScale); + break; + case ESM::FourCC<'N','P','C','O'>::value: + mInventory.add(esm); + break; + case ESM::FourCC<'N','P','C','S'>::value: + mSpells.add(esm); + break; + case ESM::FourCC<'A','I','D','T'>::value: + esm.getHExact(&mAiData, sizeof(mAiData)); + mHasAI = true; + break; + case AI_Wander: + case AI_Activate: + case AI_Escort: + case AI_Follow: + case AI_Travel: + case AI_CNDT: + mAiPackage.add(esm); + break; + default: + esm.fail("Unknown subrecord"); + } + } + if (!hasNpdt) + esm.fail("Missing NPDT subrecord"); + if (!hasFlags) + esm.fail("Missing FLAG subrecord"); } - mInventory.save(esm); - mSpells.save(esm); - if (mHasAI) { - esm.writeHNT("AIDT", mAiData, sizeof(mAiData)); + void Creature::save(ESMWriter &esm) const + { + esm.writeHNCString("MODL", mModel); + esm.writeHNOCString("CNAM", mOriginal); + esm.writeHNOCString("FNAM", mName); + esm.writeHNOCString("SCRI", mScript); + esm.writeHNT("NPDT", mData, 96); + esm.writeHNT("FLAG", mFlags); + if (mScale != 1.0) { + esm.writeHNT("XSCL", mScale); + } + + mInventory.save(esm); + mSpells.save(esm); + if (mHasAI) { + esm.writeHNT("AIDT", mAiData, sizeof(mAiData)); + } + mAiPackage.save(esm); } - mAiPackage.save(esm); -} void Creature::blank() { diff --git a/components/esm/loadench.cpp b/components/esm/loadench.cpp index 2438038337..54690d9a0b 100644 --- a/components/esm/loadench.cpp +++ b/components/esm/loadench.cpp @@ -10,8 +10,28 @@ namespace ESM void Enchantment::load(ESMReader &esm) { - esm.getHNT(mData, "ENDT", 16); - mEffects.load(esm); + mEffects.mList.clear(); + bool hasData = false; + while (esm.hasMoreSubs()) + { + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::FourCC<'E','N','D','T'>::value: + esm.getHT(mData, 16); + hasData = true; + break; + case ESM::FourCC<'E','N','A','M'>::value: + mEffects.add(esm); + break; + default: + esm.fail("Unknown subrecord"); + break; + } + } + if (!hasData) + esm.fail("Missing ENDT subrecord"); } void Enchantment::save(ESMWriter &esm) const diff --git a/components/esm/loadfact.cpp b/components/esm/loadfact.cpp index db7e5b7b44..006ca0ce00 100644 --- a/components/esm/loadfact.cpp +++ b/components/esm/loadfact.cpp @@ -28,27 +28,46 @@ namespace ESM void Faction::load(ESMReader &esm) { - mName = esm.getHNOString("FNAM"); + mReactions.clear(); + for (int i=0;i<10;++i) + mRanks[i].clear(); - // Read rank names. These are optional. - int i = 0; - while (esm.isNextSub("RNAM") && i < 10) - mRanks[i++] = esm.getHString(); - - // Main data struct - esm.getHNT(mData, "FADT", 240); - - if (mData.mIsHidden > 1) - esm.fail("Unknown flag!"); - - // Read faction response values + int rankCounter=0; + bool hasData = false; while (esm.hasMoreSubs()) { - std::string faction = esm.getHNString("ANAM"); - int reaction; - esm.getHNT(reaction, "INTV"); - mReactions[faction] = reaction; + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'R','N','A','M'>::value: + if (rankCounter >= 10) + esm.fail("Rank out of range"); + mRanks[rankCounter++] = esm.getHString(); + break; + case ESM::FourCC<'F','A','D','T'>::value: + esm.getHT(mData, 240); + if (mData.mIsHidden > 1) + esm.fail("Unknown flag!"); + hasData = true; + break; + case ESM::FourCC<'A','N','A','M'>::value: + { + std::string faction = esm.getHString(); + int reaction; + esm.getHNT(reaction, "INTV"); + mReactions[faction] = reaction; + break; + } + default: + esm.fail("Unknown subrecord"); + } } + if (!hasData) + esm.fail("Missing FADT subrecord"); } void Faction::save(ESMWriter &esm) const { diff --git a/components/esm/loadingr.cpp b/components/esm/loadingr.cpp index 5c98cb8b97..7e0cc3168d 100644 --- a/components/esm/loadingr.cpp +++ b/components/esm/loadingr.cpp @@ -8,45 +8,71 @@ namespace ESM { unsigned int Ingredient::sRecordId = REC_INGR; -void Ingredient::load(ESMReader &esm) -{ - mModel = esm.getHNString("MODL"); - mName = esm.getHNOString("FNAM"); - esm.getHNT(mData, "IRDT", 56); - mScript = esm.getHNOString("SCRI"); - mIcon = esm.getHNOString("ITEX"); - // horrible hack to fix broken data in records - for (int i=0; i<4; ++i) + void Ingredient::load(ESMReader &esm) { - if (mData.mEffectID[i] != 85 && - mData.mEffectID[i] != 22 && - mData.mEffectID[i] != 17 && - mData.mEffectID[i] != 79 && - mData.mEffectID[i] != 74) + bool hasData = false; + while (esm.hasMoreSubs()) { - mData.mAttributes[i] = -1; + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::FourCC<'M','O','D','L'>::value: + mModel = esm.getHString(); + break; + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'I','R','D','T'>::value: + esm.getHT(mData, 56); + hasData = true; + break; + case ESM::FourCC<'S','C','R','I'>::value: + mScript = esm.getHString(); + break; + case ESM::FourCC<'I','T','E','X'>::value: + mIcon = esm.getHString(); + break; + default: + esm.fail("Unknown subrecord"); + } } - // is this relevant in cycle from 0 to 4? - if (mData.mEffectID[i] != 89 && - mData.mEffectID[i] != 26 && - mData.mEffectID[i] != 21 && - mData.mEffectID[i] != 83 && - mData.mEffectID[i] != 78) + if (!hasData) + esm.fail("Missing IRDT subrecord"); + + // horrible hack to fix broken data in records + for (int i=0; i<4; ++i) { - mData.mSkills[i] = -1; + if (mData.mEffectID[i] != 85 && + mData.mEffectID[i] != 22 && + mData.mEffectID[i] != 17 && + mData.mEffectID[i] != 79 && + mData.mEffectID[i] != 74) + { + mData.mAttributes[i] = -1; + } + + // is this relevant in cycle from 0 to 4? + if (mData.mEffectID[i] != 89 && + mData.mEffectID[i] != 26 && + mData.mEffectID[i] != 21 && + mData.mEffectID[i] != 83 && + mData.mEffectID[i] != 78) + { + mData.mSkills[i] = -1; + } } } -} -void Ingredient::save(ESMWriter &esm) const -{ - esm.writeHNCString("MODL", mModel); - esm.writeHNOCString("FNAM", mName); - esm.writeHNT("IRDT", mData, 56); - esm.writeHNOCString("SCRI", mScript); - esm.writeHNOCString("ITEX", mIcon); -} + void Ingredient::save(ESMWriter &esm) const + { + esm.writeHNCString("MODL", mModel); + esm.writeHNOCString("FNAM", mName); + esm.writeHNT("IRDT", mData, 56); + esm.writeHNOCString("SCRI", mScript); + esm.writeHNOCString("ITEX", mIcon); + } void Ingredient::blank() { diff --git a/components/esm/loadlevlist.cpp b/components/esm/loadlevlist.cpp index 2cbfac0b40..ca5c5d74d8 100644 --- a/components/esm/loadlevlist.cpp +++ b/components/esm/loadlevlist.cpp @@ -7,49 +7,51 @@ namespace ESM { -void LevelledListBase::load(ESMReader &esm) -{ - esm.getHNT(mFlags, "DATA"); - esm.getHNT(mChanceNone, "NNAM"); - - if (esm.isNextSub("INDX")) + void LevelledListBase::load(ESMReader &esm) { - int len; - esm.getHT(len); - mList.resize(len); - } - else - { - esm.skipRecord(); - return; - } + esm.getHNT(mFlags, "DATA"); + esm.getHNT(mChanceNone, "NNAM"); - // If this levelled list was already loaded by a previous content file, - // we overwrite the list. Merging lists should probably be left to external tools, - // with the limited amount of information there is in the records, all merging methods - // will be flawed in some way. For a proper fix the ESM format would have to be changed - // to actually track list changes instead of including the whole list for every file - // that does something with that list. + if (esm.isNextSub("INDX")) + { + int len; + esm.getHT(len); + mList.resize(len); + } + else + { + // Original engine ignores rest of the record, even if there are items following + mList.clear(); + esm.skipRecord(); + return; + } - for (size_t i = 0; i < mList.size(); i++) - { - LevelItem &li = mList[i]; - li.mId = esm.getHNString(mRecName); - esm.getHNT(li.mLevel, "INTV"); - } -} -void LevelledListBase::save(ESMWriter &esm) const -{ - esm.writeHNT("DATA", mFlags); - esm.writeHNT("NNAM", mChanceNone); - esm.writeHNT("INDX", mList.size()); + // If this levelled list was already loaded by a previous content file, + // we overwrite the list. Merging lists should probably be left to external tools, + // with the limited amount of information there is in the records, all merging methods + // will be flawed in some way. For a proper fix the ESM format would have to be changed + // to actually track list changes instead of including the whole list for every file + // that does something with that list. - for (std::vector::const_iterator it = mList.begin(); it != mList.end(); ++it) - { - esm.writeHNCString(mRecName, it->mId); - esm.writeHNT("INTV", it->mLevel); + for (size_t i = 0; i < mList.size(); i++) + { + LevelItem &li = mList[i]; + li.mId = esm.getHNString(mRecName); + esm.getHNT(li.mLevel, "INTV"); + } + } + void LevelledListBase::save(ESMWriter &esm) const + { + esm.writeHNT("DATA", mFlags); + esm.writeHNT("NNAM", mChanceNone); + esm.writeHNT("INDX", mList.size()); + + for (std::vector::const_iterator it = mList.begin(); it != mList.end(); ++it) + { + esm.writeHNCString(mRecName, it->mId); + esm.writeHNT("INTV", it->mLevel); + } } -} void LevelledListBase::blank() { diff --git a/components/esm/loadligh.cpp b/components/esm/loadligh.cpp index f88ff09d6f..26d70d964a 100644 --- a/components/esm/loadligh.cpp +++ b/components/esm/loadligh.cpp @@ -8,25 +8,50 @@ namespace ESM { unsigned int Light::sRecordId = REC_LIGH; -void Light::load(ESMReader &esm) -{ - mModel = esm.getHNOString("MODL"); - mName = esm.getHNOString("FNAM"); - mIcon = esm.getHNOString("ITEX"); - assert(sizeof(mData) == 24); - esm.getHNT(mData, "LHDT", 24); - mScript = esm.getHNOString("SCRI"); - mSound = esm.getHNOString("SNAM"); -} -void Light::save(ESMWriter &esm) const -{ - esm.writeHNCString("MODL", mModel); - esm.writeHNOCString("FNAM", mName); - esm.writeHNOCString("ITEX", mIcon); - esm.writeHNT("LHDT", mData, 24); - esm.writeHNOCString("SCRI", mScript); - esm.writeHNOCString("SNAM", mSound); -} + void Light::load(ESMReader &esm) + { + bool hasData = false; + while (esm.hasMoreSubs()) + { + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::FourCC<'M','O','D','L'>::value: + mModel = esm.getHString(); + break; + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'I','T','E','X'>::value: + mIcon = esm.getHString(); + break; + case ESM::FourCC<'L','H','D','T'>::value: + esm.getHT(mData, 24); + hasData = true; + break; + case ESM::FourCC<'S','C','R','I'>::value: + mScript = esm.getHString(); + break; + case ESM::FourCC<'S','N','A','M'>::value: + mSound = esm.getHString(); + break; + default: + esm.fail("Unknown subrecord"); + } + } + if (!hasData) + esm.fail("Missing LHDT subrecord"); + } + void Light::save(ESMWriter &esm) const + { + esm.writeHNCString("MODL", mModel); + esm.writeHNOCString("FNAM", mName); + esm.writeHNOCString("ITEX", mIcon); + esm.writeHNT("LHDT", mData, 24); + esm.writeHNOCString("SCRI", mScript); + esm.writeHNOCString("SNAM", mSound); + } void Light::blank() { diff --git a/components/esm/loadlock.cpp b/components/esm/loadlock.cpp index 42677a22bf..2747a6f787 100644 --- a/components/esm/loadlock.cpp +++ b/components/esm/loadlock.cpp @@ -8,26 +8,48 @@ namespace ESM { unsigned int Lockpick::sRecordId = REC_LOCK; -void Lockpick::load(ESMReader &esm) -{ - mModel = esm.getHNString("MODL"); - mName = esm.getHNOString("FNAM"); + void Lockpick::load(ESMReader &esm) + { + bool hasData = true; + while (esm.hasMoreSubs()) + { + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::FourCC<'M','O','D','L'>::value: + mModel = esm.getHString(); + break; + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'L','K','D','T'>::value: + esm.getHT(mData, 16); + hasData = true; + break; + case ESM::FourCC<'S','C','R','I'>::value: + mScript = esm.getHString(); + break; + case ESM::FourCC<'I','T','E','X'>::value: + mIcon = esm.getHString(); + break; + default: + esm.fail("Unknown subrecord"); + } + } + if (!hasData) + esm.fail("Missing LKDT subrecord"); + } - esm.getHNT(mData, "LKDT", 16); + void Lockpick::save(ESMWriter &esm) const + { + esm.writeHNCString("MODL", mModel); + esm.writeHNOCString("FNAM", mName); - mScript = esm.getHNOString("SCRI"); - mIcon = esm.getHNOString("ITEX"); -} - -void Lockpick::save(ESMWriter &esm) const -{ - esm.writeHNCString("MODL", mModel); - esm.writeHNOCString("FNAM", mName); - - esm.writeHNT("LKDT", mData, 16); - esm.writeHNOString("SCRI", mScript); - esm.writeHNOCString("ITEX", mIcon); -} + esm.writeHNT("LKDT", mData, 16); + esm.writeHNOString("SCRI", mScript); + esm.writeHNOCString("ITEX", mIcon); + } void Lockpick::blank() { diff --git a/components/esm/loadmgef.cpp b/components/esm/loadmgef.cpp index 3ec07a2a91..6f859ab3cd 100644 --- a/components/esm/loadmgef.cpp +++ b/components/esm/loadmgef.cpp @@ -246,7 +246,7 @@ void MagicEffect::load(ESMReader &esm) mDescription = esm.getHString(); break; default: - esm.fail("Unknown subrecord " + esm.retSubName().toString()); + esm.fail("Unknown subrecord"); } } } diff --git a/components/esm/loadmgef.hpp b/components/esm/loadmgef.hpp index 4c7ac938a0..e66322832f 100644 --- a/components/esm/loadmgef.hpp +++ b/components/esm/loadmgef.hpp @@ -57,9 +57,9 @@ struct MagicEffect // Glow color for enchanted items with this effect int mRed, mGreen, mBlue; - float mUnknown1; + float mUnknown1; // Called "Size X" in CS float mSpeed; // Speed of fired projectile - float mUnknown2; + float mUnknown2; // Called "Size Cap" in CS }; // 36 bytes static const std::map sNames; diff --git a/components/esm/loadnpc.cpp b/components/esm/loadnpc.cpp index 3d617241b6..d90b4816dd 100644 --- a/components/esm/loadnpc.cpp +++ b/components/esm/loadnpc.cpp @@ -99,7 +99,7 @@ namespace ESM mAiPackage.add(esm); break; default: - esm.fail("Unknown subrecord " + esm.retSubName().toString()); + esm.fail("Unknown subrecord"); } } if (!hasNpdt) diff --git a/components/esm/loadprob.cpp b/components/esm/loadprob.cpp index b736bb64b5..c5f80c5844 100644 --- a/components/esm/loadprob.cpp +++ b/components/esm/loadprob.cpp @@ -8,26 +8,48 @@ namespace ESM { unsigned int Probe::sRecordId = REC_PROB; -void Probe::load(ESMReader &esm) -{ - mModel = esm.getHNString("MODL"); - mName = esm.getHNOString("FNAM"); + void Probe::load(ESMReader &esm) + { + bool hasData = true; + while (esm.hasMoreSubs()) + { + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::FourCC<'M','O','D','L'>::value: + mModel = esm.getHString(); + break; + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'P','B','D','T'>::value: + esm.getHT(mData, 16); + hasData = true; + break; + case ESM::FourCC<'S','C','R','I'>::value: + mScript = esm.getHString(); + break; + case ESM::FourCC<'I','T','E','X'>::value: + mIcon = esm.getHString(); + break; + default: + esm.fail("Unknown subrecord"); + } + } + if (!hasData) + esm.fail("Missing PBDT subrecord"); + } - esm.getHNT(mData, "PBDT", 16); + void Probe::save(ESMWriter &esm) const + { + esm.writeHNCString("MODL", mModel); + esm.writeHNOCString("FNAM", mName); - mScript = esm.getHNOString("SCRI"); - mIcon = esm.getHNOString("ITEX"); -} - -void Probe::save(ESMWriter &esm) const -{ - esm.writeHNCString("MODL", mModel); - esm.writeHNOCString("FNAM", mName); - - esm.writeHNT("PBDT", mData, 16); - esm.writeHNOString("SCRI", mScript); - esm.writeHNOCString("ITEX", mIcon); -} + esm.writeHNT("PBDT", mData, 16); + esm.writeHNOString("SCRI", mScript); + esm.writeHNOCString("ITEX", mIcon); + } void Probe::blank() { diff --git a/components/esm/loadrace.cpp b/components/esm/loadrace.cpp index 211fd5ffb3..967c0d0d73 100644 --- a/components/esm/loadrace.cpp +++ b/components/esm/loadrace.cpp @@ -43,7 +43,7 @@ void Race::load(ESMReader &esm) mPowers.add(esm); break; default: - esm.fail("Unknown subrecord " + esm.retSubName().toString()); + esm.fail("Unknown subrecord"); } } if (!hasData) diff --git a/components/esm/loadrepa.cpp b/components/esm/loadrepa.cpp index 4e6cd7794f..f90f9e39dc 100644 --- a/components/esm/loadrepa.cpp +++ b/components/esm/loadrepa.cpp @@ -10,13 +10,35 @@ namespace ESM void Repair::load(ESMReader &esm) { - mModel = esm.getHNString("MODL"); - mName = esm.getHNOString("FNAM"); - - esm.getHNT(mData, "RIDT", 16); - - mScript = esm.getHNOString("SCRI"); - mIcon = esm.getHNOString("ITEX"); + bool hasData = true; + while (esm.hasMoreSubs()) + { + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::FourCC<'M','O','D','L'>::value: + mModel = esm.getHString(); + break; + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'R','I','D','T'>::value: + esm.getHT(mData, 16); + hasData = true; + break; + case ESM::FourCC<'S','C','R','I'>::value: + mScript = esm.getHString(); + break; + case ESM::FourCC<'I','T','E','X'>::value: + mIcon = esm.getHString(); + break; + default: + esm.fail("Unknown subrecord"); + } + } + if (!hasData) + esm.fail("Missing RIDT subrecord"); } void Repair::save(ESMWriter &esm) const diff --git a/components/esm/loadscpt.cpp b/components/esm/loadscpt.cpp index 2df8e66cee..0c2bdd42ff 100644 --- a/components/esm/loadscpt.cpp +++ b/components/esm/loadscpt.cpp @@ -9,17 +9,7 @@ namespace ESM unsigned int Script::sRecordId = REC_SCPT; -void Script::load(ESMReader &esm) -{ - SCHD data; - esm.getHNT(data, "SCHD", 52); - mData = data.mData; - mId = data.mName.toString(); - - mVarNames.clear(); - - // List of local variables - if (esm.isNextSub("SCVR")) + void Script::loadSCVR(ESMReader &esm) { int s = mData.mStringTableSize; @@ -66,58 +56,70 @@ void Script::load(ESMReader &esm) } } - // Script mData - if (esm.isNextSub("SCDT")) + void Script::load(ESMReader &esm) { - mScriptData.resize(mData.mScriptDataSize); - esm.getHExact(&mScriptData[0], mScriptData.size()); - } + SCHD data; + esm.getHNT(data, "SCHD", 52); + mData = data.mData; + mId = data.mName.toString(); - // Script text - mScriptText = esm.getHNOString("SCTX"); + mVarNames.clear(); - // NOTE: A minor hack/workaround... - // - // MAO_Containers.esp from Morrowind Acoustic Overhaul has SCVR records - // at the end (see Bug #1849). Since OpenMW does not use SCVR subrecords - // for variable names just skip these as a quick fix. An alternative - // solution would be to decode and validate SCVR subrecords even if they - // appear here. - if (esm.isNextSub("SCVR")) { - esm.skipHSub(); - } -} -void Script::save(ESMWriter &esm) const -{ - std::string varNameString; - if (!mVarNames.empty()) - for (std::vector::const_iterator it = mVarNames.begin(); it != mVarNames.end(); ++it) - varNameString.append(*it); - - SCHD data; - memset(&data, 0, sizeof(data)); - - data.mData = mData; - memcpy(data.mName.name, mId.c_str(), mId.size()); - - esm.writeHNT("SCHD", data, 52); - - if (!mVarNames.empty()) - { - esm.startSubRecord("SCVR"); - for (std::vector::const_iterator it = mVarNames.begin(); it != mVarNames.end(); ++it) + while (esm.hasMoreSubs()) { - esm.writeHCString(*it); + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::FourCC<'S','C','V','R'>::value: + // list of local variables + loadSCVR(esm); + break; + case ESM::FourCC<'S','C','D','T'>::value: + // compiled script + mScriptData.resize(mData.mScriptDataSize); + esm.getHExact(&mScriptData[0], mScriptData.size()); + break; + case ESM::FourCC<'S','C','T','X'>::value: + mScriptText = esm.getHString(); + break; + default: + esm.fail("Unknown subrecord"); + } } - esm.endRecord("SCVR"); } - esm.startSubRecord("SCDT"); - esm.write(reinterpret_cast(&mScriptData[0]), mData.mScriptDataSize); - esm.endRecord("SCDT"); + void Script::save(ESMWriter &esm) const + { + std::string varNameString; + if (!mVarNames.empty()) + for (std::vector::const_iterator it = mVarNames.begin(); it != mVarNames.end(); ++it) + varNameString.append(*it); - esm.writeHNOString("SCTX", mScriptText); -} + SCHD data; + memset(&data, 0, sizeof(data)); + + data.mData = mData; + memcpy(data.mName.name, mId.c_str(), mId.size()); + + esm.writeHNT("SCHD", data, 52); + + if (!mVarNames.empty()) + { + esm.startSubRecord("SCVR"); + for (std::vector::const_iterator it = mVarNames.begin(); it != mVarNames.end(); ++it) + { + esm.writeHCString(*it); + } + esm.endRecord("SCVR"); + } + + esm.startSubRecord("SCDT"); + esm.write(reinterpret_cast(&mScriptData[0]), mData.mScriptDataSize); + esm.endRecord("SCDT"); + + esm.writeHNOString("SCTX", mScriptText); + } void Script::blank() { diff --git a/components/esm/loadscpt.hpp b/components/esm/loadscpt.hpp index d5b87b5dc2..deb71de6af 100644 --- a/components/esm/loadscpt.hpp +++ b/components/esm/loadscpt.hpp @@ -53,6 +53,9 @@ public: void blank(); ///< Set record to default state (does not touch the ID/index). + +private: + void loadSCVR(ESMReader &esm); }; } #endif diff --git a/components/esm/loadskil.cpp b/components/esm/loadskil.cpp index b6724e9381..7883b8a1a9 100644 --- a/components/esm/loadskil.cpp +++ b/components/esm/loadskil.cpp @@ -129,23 +129,47 @@ namespace ESM unsigned int Skill::sRecordId = REC_SKIL; -void Skill::load(ESMReader &esm) -{ - esm.getHNT(mIndex, "INDX"); - esm.getHNT(mData, "SKDT", 24); - mDescription = esm.getHNOString("DESC"); + void Skill::load(ESMReader &esm) + { + bool hasIndex = false; + bool hasData = false; + while (esm.hasMoreSubs()) + { + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::FourCC<'I','N','D','X'>::value: + esm.getHT(mIndex); + hasIndex = true; + break; + case ESM::FourCC<'S','K','D','T'>::value: + esm.getHT(mData, 24); + hasData = true; + break; + case ESM::FourCC<'D','E','S','C'>::value: + mDescription = esm.getHString(); + break; + default: + esm.fail("Unknown subrecord"); + } + } + if (!hasIndex) + esm.fail("Missing INDX"); + if (!hasData) + esm.fail("Missing SKDT"); - // create an ID from the index and the name (only used in the editor and likely to change in the - // future) - mId = indexToId (mIndex); -} + // create an ID from the index and the name (only used in the editor and likely to change in the + // future) + mId = indexToId (mIndex); + } -void Skill::save(ESMWriter &esm) const -{ - esm.writeHNT("INDX", mIndex); - esm.writeHNT("SKDT", mData, 24); - esm.writeHNOString("DESC", mDescription); -} + void Skill::save(ESMWriter &esm) const + { + esm.writeHNT("INDX", mIndex); + esm.writeHNT("SKDT", mData, 24); + esm.writeHNOString("DESC", mDescription); + } void Skill::blank() { diff --git a/components/esm/loadsndg.cpp b/components/esm/loadsndg.cpp index 9ab061ec25..5ee6f5245c 100644 --- a/components/esm/loadsndg.cpp +++ b/components/esm/loadsndg.cpp @@ -8,19 +8,38 @@ namespace ESM { unsigned int SoundGenerator::sRecordId = REC_SNDG; -void SoundGenerator::load(ESMReader &esm) -{ - esm.getHNT(mType, "DATA", 4); - - mCreature = esm.getHNOString("CNAM"); - mSound = esm.getHNOString("SNAM"); -} -void SoundGenerator::save(ESMWriter &esm) const -{ - esm.writeHNT("DATA", mType, 4); - esm.writeHNOCString("CNAM", mCreature); - esm.writeHNOCString("SNAM", mSound); -} + void SoundGenerator::load(ESMReader &esm) + { + bool hasData = false; + while (esm.hasMoreSubs()) + { + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::FourCC<'D','A','T','A'>::value: + esm.getHT(mType, 4); + hasData = true; + break; + case ESM::FourCC<'C','N','A','M'>::value: + mCreature = esm.getHString(); + break; + case ESM::FourCC<'S','N','A','M'>::value: + mSound = esm.getHString(); + break; + default: + esm.fail("Unknown subrecord"); + } + } + if (!hasData) + esm.fail("Missing DATA"); + } + void SoundGenerator::save(ESMWriter &esm) const + { + esm.writeHNT("DATA", mType, 4); + esm.writeHNOCString("CNAM", mCreature); + esm.writeHNOCString("SNAM", mSound); + } void SoundGenerator::blank() { diff --git a/components/esm/loadsoun.cpp b/components/esm/loadsoun.cpp index 28e4d7d9c5..690c1b4484 100644 --- a/components/esm/loadsoun.cpp +++ b/components/esm/loadsoun.cpp @@ -8,22 +8,35 @@ namespace ESM { unsigned int Sound::sRecordId = REC_SOUN; -void Sound::load(ESMReader &esm) -{ - mSound = esm.getHNOString("FNAM"); - esm.getHNT(mData, "DATA", 3); - /* - cout << "vol=" << (int)data.volume - << " min=" << (int)data.minRange - << " max=" << (int)data.maxRange - << endl; - */ -} -void Sound::save(ESMWriter &esm) const -{ - esm.writeHNOCString("FNAM", mSound); - esm.writeHNT("DATA", mData, 3); -} + void Sound::load(ESMReader &esm) + { + bool hasData = false; + while (esm.hasMoreSubs()) + { + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::FourCC<'F','N','A','M'>::value: + mSound = esm.getHString(); + break; + case ESM::FourCC<'D','A','T','A'>::value: + esm.getHT(mData, 3); + hasData = true; + break; + default: + esm.fail("Unknown subrecord"); + } + } + if (!hasData) + esm.fail("Missing DATA"); + } + + void Sound::save(ESMWriter &esm) const + { + esm.writeHNOCString("FNAM", mSound); + esm.writeHNT("DATA", mData, 3); + } void Sound::blank() { diff --git a/components/esm/loadspel.cpp b/components/esm/loadspel.cpp index 716f47e715..96c048e0a9 100644 --- a/components/esm/loadspel.cpp +++ b/components/esm/loadspel.cpp @@ -8,40 +8,41 @@ namespace ESM { unsigned int Spell::sRecordId = REC_SPEL; -void Spell::load(ESMReader &esm) -{ - bool hasData = false; - while (esm.hasMoreSubs()) + void Spell::load(ESMReader &esm) { - esm.getSubName(); - uint32_t val = esm.retSubName().val; - - switch (val) + mEffects.mList.clear(); + bool hasData = false; + while (esm.hasMoreSubs()) { - case ESM::FourCC<'F','N','A','M'>::value: - mName = esm.getHString(); - break; - case ESM::FourCC<'S','P','D','T'>::value: - esm.getHT(mData, 12); - hasData = true; - break; - case ESM::FourCC<'E','N','A','M'>::value: - ENAMstruct s; - esm.getHT(s, 24); - mEffects.mList.push_back(s); - break; - } - } - if (!hasData) - esm.fail("Missing SPDT subrecord"); -} + esm.getSubName(); + uint32_t val = esm.retSubName().val; -void Spell::save(ESMWriter &esm) const -{ - esm.writeHNOCString("FNAM", mName); - esm.writeHNT("SPDT", mData, 12); - mEffects.save(esm); -} + switch (val) + { + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'S','P','D','T'>::value: + esm.getHT(mData, 12); + hasData = true; + break; + case ESM::FourCC<'E','N','A','M'>::value: + ENAMstruct s; + esm.getHT(s, 24); + mEffects.mList.push_back(s); + break; + } + } + if (!hasData) + esm.fail("Missing SPDT subrecord"); + } + + void Spell::save(ESMWriter &esm) const + { + esm.writeHNOCString("FNAM", mName); + esm.writeHNT("SPDT", mData, 12); + mEffects.save(esm); + } void Spell::blank() { diff --git a/components/esm/loadsscr.cpp b/components/esm/loadsscr.cpp index 69b04bb237..816075b7e3 100644 --- a/components/esm/loadsscr.cpp +++ b/components/esm/loadsscr.cpp @@ -8,15 +8,37 @@ namespace ESM { unsigned int StartScript::sRecordId = REC_SSCR; -void StartScript::load(ESMReader &esm) -{ - mData = esm.getHNString("DATA"); - mScript = esm.getHNString("NAME"); -} -void StartScript::save(ESMWriter &esm) const -{ - esm.writeHNString("DATA", mData); - esm.writeHNString("NAME", mScript); -} + void StartScript::load(ESMReader &esm) + { + bool hasData = false; + bool hasName = false; + while (esm.hasMoreSubs()) + { + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::FourCC<'D','A','T','A'>::value: + mData = esm.getHString(); + hasData = true; + break; + case ESM::FourCC<'N','A','M','E'>::value: + mScript = esm.getHString(); + hasName = true; + break; + default: + esm.fail("Unknown subrecord"); + } + } + if (!hasData) + esm.fail("Missing DATA"); + if (!hasName) + esm.fail("Missing NAME"); + } + void StartScript::save(ESMWriter &esm) const + { + esm.writeHNString("DATA", mData); + esm.writeHNString("NAME", mScript); + } } diff --git a/components/esm/loadstat.cpp b/components/esm/loadstat.cpp index 53d1b4bb59..2bb817332d 100644 --- a/components/esm/loadstat.cpp +++ b/components/esm/loadstat.cpp @@ -8,15 +8,16 @@ namespace ESM { unsigned int Static::sRecordId = REC_STAT; -void Static::load(ESMReader &esm) -{ - mPersistent = esm.getRecordFlags() & 0x0400; - mModel = esm.getHNString("MODL"); -} -void Static::save(ESMWriter &esm) const -{ - esm.writeHNCString("MODL", mModel); -} + void Static::load(ESMReader &esm) + { + mPersistent = esm.getRecordFlags() & 0x0400; + + mModel = esm.getHNString("MODL"); + } + void Static::save(ESMWriter &esm) const + { + esm.writeHNCString("MODL", mModel); + } void Static::blank() { diff --git a/components/esm/loadtes3.hpp b/components/esm/loadtes3.hpp index 2c63e36917..f2dde4d1f0 100644 --- a/components/esm/loadtes3.hpp +++ b/components/esm/loadtes3.hpp @@ -50,8 +50,8 @@ namespace ESM NAME32 mPlayerName; }; GMDT mGameData; // Used in .ess savegames only - std::vector mSCRD; // Used in .ess savegames only, screenshot? - std::vector mSCRS; // Used in .ess savegames only, screenshot? + std::vector mSCRD; // Used in .ess savegames only, unknown + std::vector mSCRS; // Used in .ess savegames only, screenshot Data mData; int mFormat; diff --git a/components/esm/spelllist.cpp b/components/esm/spelllist.cpp index 98bd53ae6a..71c7b340d2 100644 --- a/components/esm/spelllist.cpp +++ b/components/esm/spelllist.cpp @@ -5,14 +5,6 @@ namespace ESM { -void SpellList::load(ESMReader &esm) -{ - mList.clear(); - while (esm.isNextSub("NPCS")) { - add(esm); - } -} - void SpellList::add(ESMReader &esm) { mList.push_back(esm.getHString()); diff --git a/components/esm/spelllist.hpp b/components/esm/spelllist.hpp index 9493199a80..6fb0980659 100644 --- a/components/esm/spelllist.hpp +++ b/components/esm/spelllist.hpp @@ -11,6 +11,7 @@ namespace ESM /** A list of references to spells and spell effects. This is shared between the records BSGN, NPC and RACE. + NPCS subrecord. */ struct SpellList { @@ -22,9 +23,6 @@ namespace ESM /// Load one spell, assumes the subrecord name was already read void add(ESMReader &esm); - /// Load all spells - /// TODO: remove this method, the ESM format doesn't guarantee that all spell subrecords follow one another - void load(ESMReader &esm); void save(ESMWriter &esm) const; }; } diff --git a/components/files/androidpath.cpp b/components/files/androidpath.cpp index 1176260952..009bd98a2c 100644 --- a/components/files/androidpath.cpp +++ b/components/files/androidpath.cpp @@ -27,7 +27,7 @@ char const * Buffer::getData() } -JNIEXPORT void JNICALL Java_org_libsdl_app_SDLActivity_getPathToJni(JNIEnv *env, jobject obj, jstring prompt) +JNIEXPORT void JNICALL Java_ui_activity_GameActivity_getPathToJni(JNIEnv *env, jobject obj, jstring prompt) { jboolean iscopy; Buffer::setData((env)->GetStringUTFChars(prompt, &iscopy)); diff --git a/components/files/androidpath.h b/components/files/androidpath.h index 3157c067f5..a93a160e0f 100644 --- a/components/files/androidpath.h +++ b/components/files/androidpath.h @@ -1,8 +1,8 @@ /* DO NOT EDIT THIS FILE - it is machine generated */ #include -#ifndef _Included_org_libsdl_app_SDLActivity_getPathToJni -#define _Included_org_libsdl_app_SDLActivity_getPathToJni +#ifndef _Included_ui_activity_GameActivity_getPathToJni +#define _Included_ui_activity_GameActivity_getPathToJni #ifdef __cplusplus extern "C" { #endif @@ -11,7 +11,7 @@ extern "C" { * Method: getPathToJni * Signature: (I)I */ -JNIEXPORT void JNICALL Java_org_libsdl_app_SDLActivity_getPathToJni(JNIEnv *env, jobject obj, jstring prompt); +JNIEXPORT void JNICALL Java_ui_activity_GameActivity_getPathToJni(JNIEnv *env, jobject obj, jstring prompt); #ifdef __cplusplus } diff --git a/components/nif/.gitignore b/components/nif/.gitignore deleted file mode 100644 index 731498d9a1..0000000000 --- a/components/nif/.gitignore +++ /dev/null @@ -1 +0,0 @@ -old_d diff --git a/components/widgets/list.cpp b/components/widgets/list.cpp index 28271e87df..f3d3a2c288 100644 --- a/components/widgets/list.cpp +++ b/components/widgets/list.cpp @@ -152,9 +152,9 @@ namespace Gui eventWidgetSelected(_sender); } - MyGUI::Widget* MWList::getItemWidget(const std::string& name) + MyGUI::Button *MWList::getItemWidget(const std::string& name) { - return mScrollView->findWidget (getName() + "_item_" + name); + return mScrollView->findWidget (getName() + "_item_" + name)->castType(); } } diff --git a/components/widgets/list.hpp b/components/widgets/list.hpp index 093cd8c186..72c8a733c1 100644 --- a/components/widgets/list.hpp +++ b/components/widgets/list.hpp @@ -43,7 +43,7 @@ namespace Gui std::string getItemNameAt(unsigned int at); ///< \attention if there are separators, this method will return "" at the place where the separator is void clear(); - MyGUI::Widget* getItemWidget(const std::string& name); + MyGUI::Button* getItemWidget(const std::string& name); ///< get widget for an item name, useful to set up tooltip virtual void setPropertyOverride(const std::string& _key, const std::string& _value); diff --git a/files/materials/objects.shader b/files/materials/objects.shader index 2368d99616..828674cc72 100644 --- a/files/materials/objects.shader +++ b/files/materials/objects.shader @@ -243,7 +243,9 @@ } #else - +#if NORMAL_MAP && SH_GLSLES + mat3 transpose( mat3 m); +#endif // ----------------------------------- FRAGMENT ------------------------------------------ #if UNDERWATER @@ -376,13 +378,13 @@ float3 binormal = cross(tangentPassthrough.xyz, normal.xyz); float3x3 tbn = float3x3(tangentPassthrough.xyz, binormal, normal.xyz); - #if SH_GLSL + #if SH_GLSL || SH_GLSLES tbn = transpose(tbn); #endif float4 normalTex = shSample(normalMap, UV.xy); - normal = normalize (shMatrixMult( transpose(tbn), normalTex.xyz * 2 - 1 )); + normal = normalize (shMatrixMult( transpose(tbn), normalTex.xyz * 2.0 - float3 (1.0,1.0,1.0) )); #endif #if ENV_MAP || SPECULAR || PARALLAX @@ -576,5 +578,14 @@ // prevent negative colour output (for example with negative lights) shOutputColour(0).xyz = max(shOutputColour(0).xyz, float3(0.0,0.0,0.0)); } +#if NORMAL_MAP && SH_GLSLES + mat3 transpose(mat3 m){ + return mat3( + m[0][0],m[1][0],m[2][0], + m[0][1],m[1][1],m[2][1], + m[0][2],m[1][2],m[2][2] + ); + } +#endif #endif diff --git a/files/materials/ripples.particle b/files/materials/ripples.particle index e8402b6914..58045f6d73 100644 --- a/files/materials/ripples.particle +++ b/files/materials/ripples.particle @@ -1,8 +1,8 @@ particle_system openmw/Ripples { material openmw/Ripple - particle_width 50 - particle_height 50 + particle_width 30 + particle_height 30 // To make the particles move with the scene node when the waterlevel changes local_space true quota 300 @@ -17,7 +17,7 @@ particle_system openmw/Ripples affector Scaler { - rate 100 + rate 120 } affector Rotator diff --git a/files/materials/terrain.shader b/files/materials/terrain.shader index a4ca10fcc3..9bb5d247da 100644 --- a/files/materials/terrain.shader +++ b/files/materials/terrain.shader @@ -221,6 +221,9 @@ #if UNDERWATER #include "underwater.h" #endif +#if NORMAL_MAP && SH_GLSLES + mat3 transpose(mat3 m); +#endif SH_BEGIN_PROGRAM @@ -319,7 +322,7 @@ shUniform(float4, cameraPos) @shAutoConstant(cameraPos, camera_position) // derive final matrix float3x3 tbn = float3x3(tangent, binormal, normal); - #if SH_GLSL + #if SH_GLSL || SH_GLSLES tbn = transpose(tbn); #endif #endif @@ -492,5 +495,13 @@ albedo = shLerp(albedo, diffuseTex, blendValues@shPropertyString(blendmap_compon shOutputColour(0).a = 1.0-previousAlpha; #endif } - +#if NORMAL_MAP && SH_GLSLES + mat3 transpose(mat3 m){ + return mat3( + m[0][0],m[1][0],m[2][0], + m[0][1],m[1][1],m[2][1], + m[0][2],m[1][2],m[2][2] + ); + } +#endif #endif diff --git a/files/mygui/openmw_hud.layout b/files/mygui/openmw_hud.layout index 84fd9d247b..8334e34b96 100644 --- a/files/mygui/openmw_hud.layout +++ b/files/mygui/openmw_hud.layout @@ -112,7 +112,8 @@ - + + diff --git a/files/mygui/openmw_journal.skin.xml b/files/mygui/openmw_journal.skin.xml index 942c9a4d40..42be2bb629 100644 --- a/files/mygui/openmw_journal.skin.xml +++ b/files/mygui/openmw_journal.skin.xml @@ -28,9 +28,9 @@ - - - + + + diff --git a/files/mygui/openmw_stats_window.layout b/files/mygui/openmw_stats_window.layout index 11119c4044..1d9b75b9be 100644 --- a/files/mygui/openmw_stats_window.layout +++ b/files/mygui/openmw_stats_window.layout @@ -235,7 +235,9 @@ - + + + diff --git a/files/ui/settingspage.ui b/files/ui/settingspage.ui index 6c873ea926..7f5e4a7de8 100644 --- a/files/ui/settingspage.ui +++ b/files/ui/settingspage.ui @@ -131,6 +131,13 @@ + + + + 4 + + +