diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a59aca8e..205adc1dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ Bug #1990: Sunrise/sunset not set correct Bug #2222: Fatigue's effect on selling price is backwards Bug #2326: After a bound item expires the last equipped item of that type is not automatically re-equipped + Bug #2772: Non-existing class or faction freezes the game Bug #2835: Player able to slowly move when overencumbered Bug #2971: Compiler did not reject lines with naked expressions beginning with x.y Bug #3374: Touch spells not hitting kwama foragers diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 2fe9ebcad..ac5608b8e 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -120,7 +120,7 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) } } -void ESMStore::setUp() +void ESMStore::setUp(bool validateRecords) { mIds.clear(); @@ -142,6 +142,62 @@ void ESMStore::setUp() mAttributes.setUp(); mDialogs.setUp(); mStatics.setUp(); + + if (validateRecords) + validate(); +} + +void ESMStore::validate() +{ + // Cache first class from store - we will use it if current class is not found + std::string defaultCls = ""; + Store::iterator it = mClasses.begin(); + if (it != mClasses.end()) + defaultCls = it->mId; + else + throw std::runtime_error("List of NPC classes is empty!"); + + // Validate NPCs for non-existing class and faction. + // We will replace invalid entries by fixed ones + std::vector entitiesToReplace; + for (ESM::NPC npc : mNpcs) + { + bool changed = false; + + const std::string npcFaction = npc.mFaction; + if (!npcFaction.empty()) + { + const ESM::Faction *fact = mFactions.search(npcFaction); + if (!fact) + { + std::cerr << "NPC '" << npc.mId << "' (" << npc.mName << ") has nonexistent faction '" << npc.mFaction << "', ignoring it." << std::endl; + npc.mFaction = ""; + npc.mNpdt.mRank = -1; + changed = true; + } + } + + std::string npcClass = npc.mClass; + if (!npcClass.empty()) + { + const ESM::Class *cls = mClasses.search(npcClass); + if (!cls) + { + std::cerr << "NPC '" << npc.mId << "' (" << npc.mName << ") has nonexistent class '" << npc.mClass << "', using '" << defaultCls << "' class as replacement." << std::endl; + npc.mClass = defaultCls; + changed = true; + } + } + + if (changed) + entitiesToReplace.push_back(npc); + } + + for (const ESM::NPC &npc : entitiesToReplace) + { + mNpcs.eraseStatic(npc.mId); + mNpcs.insertStatic(npc); + } } int ESMStore::countSavedGameRecords() const diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index a14f6368e..f4d792118 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -74,6 +74,9 @@ namespace MWWorld unsigned int mDynamicCount; + /// Validate entries in store after setup + void validate(); + public: /// \todo replace with SharedIterator typedef std::map::const_iterator iterator; @@ -228,7 +231,7 @@ namespace MWWorld // This method must be called once, after loading all master/plugin files. This can only be done // from the outside, so it must be public. - void setUp(); + void setUp(bool validateRecords = false); int countSavedGameRecords() const; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 000bdfa1a..3d7ecb0e3 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -184,7 +184,7 @@ namespace MWWorld fillGlobalVariables(); - mStore.setUp(); + mStore.setUp(true); mStore.movePlayerRecord(); mSwimHeightScale = mStore.get().find("fSwimHeightScale")->getFloat();