mirror of
https://github.com/OpenMW/openmw.git
synced 2025-02-05 16:45:34 +00:00
Merge branch 'drop_the_bird' into 'master'
Disallow inserting containers, creatures, and npcs from the save game not present in content files Closes #5884 See merge request OpenMW/openmw!683
This commit is contained in:
commit
5fce5b12f4
8 changed files with 92 additions and 57 deletions
|
@ -443,6 +443,8 @@ namespace MWBase
|
||||||
virtual void applyDeferredPreviewRotationToPlayer(float dt) = 0;
|
virtual void applyDeferredPreviewRotationToPlayer(float dt) = 0;
|
||||||
virtual void disableDeferredPreviewRotation() = 0;
|
virtual void disableDeferredPreviewRotation() = 0;
|
||||||
|
|
||||||
|
virtual void saveLoaded() = 0;
|
||||||
|
|
||||||
virtual void setupPlayer() = 0;
|
virtual void setupPlayer() = 0;
|
||||||
virtual void renderPlayer() = 0;
|
virtual void renderPlayer() = 0;
|
||||||
|
|
||||||
|
|
|
@ -505,6 +505,7 @@ void MWState::StateManager::loadGame (const Character *character, const std::str
|
||||||
character->getPath().filename().string());
|
character->getPath().filename().string());
|
||||||
|
|
||||||
MWBase::Environment::get().getWindowManager()->setNewGame(false);
|
MWBase::Environment::get().getWindowManager()->setNewGame(false);
|
||||||
|
MWBase::Environment::get().getWorld()->saveLoaded();
|
||||||
MWBase::Environment::get().getWorld()->setupPlayer();
|
MWBase::Environment::get().getWorld()->setupPlayer();
|
||||||
MWBase::Environment::get().getWorld()->renderPlayer();
|
MWBase::Environment::get().getWorld()->renderPlayer();
|
||||||
MWBase::Environment::get().getWindowManager()->updatePlayer();
|
MWBase::Environment::get().getWindowManager()->updatePlayer();
|
||||||
|
|
|
@ -48,6 +48,57 @@ namespace
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<ESM::NPC> getNPCsToReplace(const MWWorld::Store<ESM::Faction>& factions, const MWWorld::Store<ESM::Class>& classes, const std::map<std::string, ESM::NPC>& npcs)
|
||||||
|
{
|
||||||
|
// Cache first class from store - we will use it if current class is not found
|
||||||
|
std::string defaultCls;
|
||||||
|
auto it = classes.begin();
|
||||||
|
if (it != classes.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> npcsToReplace;
|
||||||
|
|
||||||
|
for (auto it : npcs)
|
||||||
|
{
|
||||||
|
ESM::NPC npc = it.second;
|
||||||
|
bool changed = false;
|
||||||
|
|
||||||
|
const std::string npcFaction = npc.mFaction;
|
||||||
|
if (!npcFaction.empty())
|
||||||
|
{
|
||||||
|
const ESM::Faction *fact = factions.search(npcFaction);
|
||||||
|
if (!fact)
|
||||||
|
{
|
||||||
|
Log(Debug::Verbose) << "NPC '" << npc.mId << "' (" << npc.mName << ") has nonexistent faction '" << npc.mFaction << "', ignoring it.";
|
||||||
|
npc.mFaction.clear();
|
||||||
|
npc.mNpdt.mRank = 0;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string npcClass = npc.mClass;
|
||||||
|
if (!npcClass.empty())
|
||||||
|
{
|
||||||
|
const ESM::Class *cls = classes.search(npcClass);
|
||||||
|
if (!cls)
|
||||||
|
{
|
||||||
|
Log(Debug::Verbose) << "NPC '" << npc.mId << "' (" << npc.mName << ") has nonexistent class '" << npc.mClass << "', using '" << defaultCls << "' class as replacement.";
|
||||||
|
npc.mClass = defaultCls;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed)
|
||||||
|
npcsToReplace.push_back(npc);
|
||||||
|
}
|
||||||
|
|
||||||
|
return npcsToReplace;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace MWWorld
|
namespace MWWorld
|
||||||
|
@ -218,49 +269,7 @@ int ESMStore::getRefCount(const std::string& id) const
|
||||||
|
|
||||||
void ESMStore::validate()
|
void ESMStore::validate()
|
||||||
{
|
{
|
||||||
// Cache first class from store - we will use it if current class is not found
|
std::vector<ESM::NPC> npcsToReplace = getNPCsToReplace(mFactions, mClasses, mNpcs.mStatic);
|
||||||
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> npcsToReplace;
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
Log(Debug::Verbose) << "NPC '" << npc.mId << "' (" << npc.mName << ") has nonexistent faction '" << npc.mFaction << "', ignoring it.";
|
|
||||||
npc.mFaction.clear();
|
|
||||||
npc.mNpdt.mRank = 0;
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string npcClass = npc.mClass;
|
|
||||||
if (!npcClass.empty())
|
|
||||||
{
|
|
||||||
const ESM::Class *cls = mClasses.search(npcClass);
|
|
||||||
if (!cls)
|
|
||||||
{
|
|
||||||
Log(Debug::Verbose) << "NPC '" << npc.mId << "' (" << npc.mName << ") has nonexistent class '" << npc.mClass << "', using '" << defaultCls << "' class as replacement.";
|
|
||||||
npc.mClass = defaultCls;
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (changed)
|
|
||||||
npcsToReplace.push_back(npc);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const ESM::NPC &npc : npcsToReplace)
|
for (const ESM::NPC &npc : npcsToReplace)
|
||||||
{
|
{
|
||||||
|
@ -331,6 +340,14 @@ void ESMStore::validate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ESMStore::validateDynamic()
|
||||||
|
{
|
||||||
|
std::vector<ESM::NPC> npcsToReplace = getNPCsToReplace(mFactions, mClasses, mNpcs.mDynamic);
|
||||||
|
|
||||||
|
for (const ESM::NPC &npc : npcsToReplace)
|
||||||
|
mNpcs.insert(npc);
|
||||||
|
}
|
||||||
|
|
||||||
int ESMStore::countSavedGameRecords() const
|
int ESMStore::countSavedGameRecords() const
|
||||||
{
|
{
|
||||||
return 1 // DYNA (dynamic name counter)
|
return 1 // DYNA (dynamic name counter)
|
||||||
|
@ -384,12 +401,14 @@ void ESMStore::validate()
|
||||||
case ESM::REC_ENCH:
|
case ESM::REC_ENCH:
|
||||||
case ESM::REC_SPEL:
|
case ESM::REC_SPEL:
|
||||||
case ESM::REC_WEAP:
|
case ESM::REC_WEAP:
|
||||||
case ESM::REC_NPC_:
|
|
||||||
case ESM::REC_LEVI:
|
case ESM::REC_LEVI:
|
||||||
case ESM::REC_LEVC:
|
case ESM::REC_LEVC:
|
||||||
|
mStores[type]->read (reader);
|
||||||
|
return true;
|
||||||
|
case ESM::REC_NPC_:
|
||||||
case ESM::REC_CREA:
|
case ESM::REC_CREA:
|
||||||
case ESM::REC_CONT:
|
case ESM::REC_CONT:
|
||||||
mStores[type]->read (reader);
|
mStores[type]->read (reader, true);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case ESM::REC_DYNA:
|
case ESM::REC_DYNA:
|
||||||
|
|
|
@ -80,8 +80,6 @@ namespace MWWorld
|
||||||
|
|
||||||
std::map<int, StoreBase *> mStores;
|
std::map<int, StoreBase *> mStores;
|
||||||
|
|
||||||
ESM::NPC mPlayerTemplate;
|
|
||||||
|
|
||||||
unsigned int mDynamicCount;
|
unsigned int mDynamicCount;
|
||||||
|
|
||||||
mutable std::map<std::string, std::weak_ptr<MWMechanics::SpellList> > mSpellListCache;
|
mutable std::map<std::string, std::weak_ptr<MWMechanics::SpellList> > mSpellListCache;
|
||||||
|
@ -172,16 +170,18 @@ namespace MWWorld
|
||||||
for (std::map<int, StoreBase *>::iterator it = mStores.begin(); it != mStores.end(); ++it)
|
for (std::map<int, StoreBase *>::iterator it = mStores.begin(); it != mStores.end(); ++it)
|
||||||
it->second->clearDynamic();
|
it->second->clearDynamic();
|
||||||
|
|
||||||
mNpcs.insert(mPlayerTemplate);
|
movePlayerRecord();
|
||||||
}
|
}
|
||||||
|
|
||||||
void movePlayerRecord ()
|
void movePlayerRecord ()
|
||||||
{
|
{
|
||||||
mPlayerTemplate = *mNpcs.find("player");
|
auto player = mNpcs.find("player");
|
||||||
mNpcs.eraseStatic(mPlayerTemplate.mId);
|
mNpcs.insert(*player);
|
||||||
mNpcs.insert(mPlayerTemplate);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Validate entries in store after loading a save
|
||||||
|
void validateDynamic();
|
||||||
|
|
||||||
void load(ESM::ESMReader &esm, Loading::Listener* listener);
|
void load(ESM::ESMReader &esm, Loading::Listener* listener);
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
|
|
|
@ -250,9 +250,15 @@ namespace MWWorld
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
template<typename T>
|
template<typename T>
|
||||||
T *Store<T>::insert(const T &item)
|
T *Store<T>::insert(const T &item, bool overrideOnly)
|
||||||
{
|
{
|
||||||
std::string id = Misc::StringUtils::lowerCase(item.mId);
|
std::string id = Misc::StringUtils::lowerCase(item.mId);
|
||||||
|
if(overrideOnly)
|
||||||
|
{
|
||||||
|
auto it = mStatic.find(id);
|
||||||
|
if(it == mStatic.end())
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
std::pair<typename Dynamic::iterator, bool> result =
|
std::pair<typename Dynamic::iterator, bool> result =
|
||||||
mDynamic.insert(std::pair<std::string, T>(id, item));
|
mDynamic.insert(std::pair<std::string, T>(id, item));
|
||||||
T *ptr = &result.first->second;
|
T *ptr = &result.first->second;
|
||||||
|
@ -337,13 +343,13 @@ namespace MWWorld
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
template<typename T>
|
template<typename T>
|
||||||
RecordId Store<T>::read(ESM::ESMReader& reader)
|
RecordId Store<T>::read(ESM::ESMReader& reader, bool overrideOnly)
|
||||||
{
|
{
|
||||||
T record;
|
T record;
|
||||||
bool isDeleted = false;
|
bool isDeleted = false;
|
||||||
|
|
||||||
record.load (reader, isDeleted);
|
record.load (reader, isDeleted);
|
||||||
insert (record);
|
insert (record, overrideOnly);
|
||||||
|
|
||||||
return RecordId(record.mId, isDeleted);
|
return RecordId(record.mId, isDeleted);
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,7 @@ namespace MWWorld
|
||||||
|
|
||||||
virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) const {}
|
virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) const {}
|
||||||
|
|
||||||
virtual RecordId read (ESM::ESMReader& reader) { return RecordId(); }
|
virtual RecordId read (ESM::ESMReader& reader, bool overrideOnly = false) { return RecordId(); }
|
||||||
///< Read into dynamic storage
|
///< Read into dynamic storage
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -192,7 +192,7 @@ namespace MWWorld
|
||||||
/// @note The record identifiers are listed in the order that the records were defined by the content files.
|
/// @note The record identifiers are listed in the order that the records were defined by the content files.
|
||||||
void listIdentifier(std::vector<std::string> &list) const override;
|
void listIdentifier(std::vector<std::string> &list) const override;
|
||||||
|
|
||||||
T *insert(const T &item);
|
T *insert(const T &item, bool overrideOnly = false);
|
||||||
T *insertStatic(const T &item);
|
T *insertStatic(const T &item);
|
||||||
|
|
||||||
bool eraseStatic(const std::string &id) override;
|
bool eraseStatic(const std::string &id) override;
|
||||||
|
@ -201,7 +201,7 @@ namespace MWWorld
|
||||||
|
|
||||||
RecordId load(ESM::ESMReader &esm) override;
|
RecordId load(ESM::ESMReader &esm) override;
|
||||||
void write(ESM::ESMWriter& writer, Loading::Listener& progress) const override;
|
void write(ESM::ESMWriter& writer, Loading::Listener& progress) const override;
|
||||||
RecordId read(ESM::ESMReader& reader) override;
|
RecordId read(ESM::ESMReader& reader, bool overrideOnly = false) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
|
|
|
@ -2473,6 +2473,11 @@ namespace MWWorld
|
||||||
mRendering->getCamera()->adjustCameraDistance(dist);
|
mRendering->getCamera()->adjustCameraDistance(dist);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void World::saveLoaded()
|
||||||
|
{
|
||||||
|
mStore.validateDynamic();
|
||||||
|
}
|
||||||
|
|
||||||
void World::setupPlayer()
|
void World::setupPlayer()
|
||||||
{
|
{
|
||||||
const ESM::NPC *player = mStore.get<ESM::NPC>().find("player");
|
const ESM::NPC *player = mStore.get<ESM::NPC>().find("player");
|
||||||
|
|
|
@ -549,6 +549,8 @@ namespace MWWorld
|
||||||
void applyDeferredPreviewRotationToPlayer(float dt) override;
|
void applyDeferredPreviewRotationToPlayer(float dt) override;
|
||||||
void disableDeferredPreviewRotation() override;
|
void disableDeferredPreviewRotation() override;
|
||||||
|
|
||||||
|
void saveLoaded() override;
|
||||||
|
|
||||||
void setupPlayer() override;
|
void setupPlayer() override;
|
||||||
void renderPlayer() override;
|
void renderPlayer() override;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue