Add NPC validation to esmstore (bug #2772)

pull/456/head
Andrei Kortunov 7 years ago
parent ccfc07e7e3
commit 49ba00a3ec

@ -4,6 +4,7 @@
Bug #1990: Sunrise/sunset not set correct Bug #1990: Sunrise/sunset not set correct
Bug #2222: Fatigue's effect on selling price is backwards 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 #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 #2835: Player able to slowly move when overencumbered
Bug #2971: Compiler did not reject lines with naked expressions beginning with x.y Bug #2971: Compiler did not reject lines with naked expressions beginning with x.y
Bug #3374: Touch spells not hitting kwama foragers Bug #3374: Touch spells not hitting kwama foragers

@ -120,7 +120,7 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener)
} }
} }
void ESMStore::setUp() void ESMStore::setUp(bool validateRecords)
{ {
mIds.clear(); mIds.clear();
@ -142,6 +142,62 @@ void ESMStore::setUp()
mAttributes.setUp(); mAttributes.setUp();
mDialogs.setUp(); mDialogs.setUp();
mStatics.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<ESM::Class>::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<ESM::NPC> 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 int ESMStore::countSavedGameRecords() const

@ -74,6 +74,9 @@ namespace MWWorld
unsigned int mDynamicCount; unsigned int mDynamicCount;
/// Validate entries in store after setup
void validate();
public: public:
/// \todo replace with SharedIterator<StoreBase> /// \todo replace with SharedIterator<StoreBase>
typedef std::map<int, StoreBase *>::const_iterator iterator; typedef std::map<int, StoreBase *>::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 // 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. // from the outside, so it must be public.
void setUp(); void setUp(bool validateRecords = false);
int countSavedGameRecords() const; int countSavedGameRecords() const;

@ -184,7 +184,7 @@ namespace MWWorld
fillGlobalVariables(); fillGlobalVariables();
mStore.setUp(); mStore.setUp(true);
mStore.movePlayerRecord(); mStore.movePlayerRecord();
mSwimHeightScale = mStore.get<ESM::GameSetting>().find("fSwimHeightScale")->getFloat(); mSwimHeightScale = mStore.get<ESM::GameSetting>().find("fSwimHeightScale")->getFloat();

Loading…
Cancel
Save