2012-10-01 19:17:04 +04:00
|
|
|
#include "esmstore.hpp"
|
|
|
|
|
2021-04-06 23:59:58 +02:00
|
|
|
#include <algorithm>
|
2021-09-26 15:21:46 +02:00
|
|
|
#include <fstream>
|
2022-09-03 11:08:23 +02:00
|
|
|
#include <tuple>
|
2010-05-17 17:35:42 +02:00
|
|
|
|
2018-08-14 23:05:43 +04:00
|
|
|
#include <components/debug/debuglog.hpp>
|
2022-01-22 15:58:41 +01:00
|
|
|
#include <components/esm3/esmreader.hpp>
|
|
|
|
#include <components/esm3/esmwriter.hpp>
|
2021-09-26 15:21:46 +02:00
|
|
|
#include <components/loadinglistener/loadinglistener.hpp>
|
|
|
|
#include <components/lua/configuration.hpp>
|
2021-04-06 23:59:58 +02:00
|
|
|
#include <components/misc/algorithm.hpp>
|
2022-06-01 22:53:18 +02:00
|
|
|
#include <components/esm3/readerscache.hpp>
|
2022-06-03 18:59:08 +02:00
|
|
|
#include <components/esmloader/load.hpp>
|
2022-07-22 21:16:36 +02:00
|
|
|
#include <components/esm4/common.hpp>
|
2015-01-25 01:53:20 +01:00
|
|
|
|
2020-07-28 08:33:28 +02:00
|
|
|
#include "../mwmechanics/spelllist.hpp"
|
|
|
|
|
2020-07-26 11:07:18 +02:00
|
|
|
namespace
|
|
|
|
{
|
2021-04-06 23:59:58 +02:00
|
|
|
struct Ref
|
|
|
|
{
|
|
|
|
ESM::RefNum mRefNum;
|
|
|
|
std::size_t mRefID;
|
|
|
|
|
|
|
|
Ref(ESM::RefNum refNum, std::size_t refID) : mRefNum(refNum), mRefID(refID) {}
|
|
|
|
};
|
|
|
|
|
|
|
|
constexpr std::size_t deletedRefID = std::numeric_limits<std::size_t>::max();
|
|
|
|
|
2022-06-01 22:53:18 +02:00
|
|
|
void readRefs(const ESM::Cell& cell, std::vector<Ref>& refs, std::vector<std::string>& refIDs, ESM::ReadersCache& readers)
|
2020-07-26 11:07:18 +02:00
|
|
|
{
|
2021-11-02 13:46:41 +00:00
|
|
|
// TODO: we have many similar copies of this code.
|
2020-07-26 11:07:18 +02:00
|
|
|
for (size_t i = 0; i < cell.mContextList.size(); i++)
|
|
|
|
{
|
2022-06-01 22:53:18 +02:00
|
|
|
const std::size_t index = static_cast<std::size_t>(cell.mContextList[i].index);
|
|
|
|
const ESM::ReadersCache::BusyItem reader = readers.get(index);
|
|
|
|
cell.restore(*reader, i);
|
2020-07-26 11:07:18 +02:00
|
|
|
ESM::CellRef ref;
|
2021-01-22 15:48:37 +01:00
|
|
|
ref.mRefNum.unset();
|
2020-07-26 11:07:18 +02:00
|
|
|
bool deleted = false;
|
2022-06-01 22:53:18 +02:00
|
|
|
while (cell.getNextRef(*reader, ref, deleted))
|
2020-07-26 11:07:18 +02:00
|
|
|
{
|
|
|
|
if(deleted)
|
2021-04-06 23:59:58 +02:00
|
|
|
refs.emplace_back(ref.mRefNum, deletedRefID);
|
2020-07-26 11:07:18 +02:00
|
|
|
else if (std::find(cell.mMovedRefs.begin(), cell.mMovedRefs.end(), ref.mRefNum) == cell.mMovedRefs.end())
|
|
|
|
{
|
2021-04-06 23:59:58 +02:00
|
|
|
refs.emplace_back(ref.mRefNum, refIDs.size());
|
|
|
|
refIDs.push_back(std::move(ref.mRefID));
|
2020-07-26 11:07:18 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-04-06 23:59:58 +02:00
|
|
|
for(const auto& [value, deleted] : cell.mLeasedRefs)
|
2020-07-26 11:07:18 +02:00
|
|
|
{
|
|
|
|
if(deleted)
|
2021-04-06 23:59:58 +02:00
|
|
|
refs.emplace_back(value.mRefNum, deletedRefID);
|
2020-07-26 11:07:18 +02:00
|
|
|
else
|
|
|
|
{
|
2021-04-06 23:59:58 +02:00
|
|
|
refs.emplace_back(value.mRefNum, refIDs.size());
|
|
|
|
refIDs.push_back(value.mRefID);
|
2020-07-26 11:07:18 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-03-23 21:07:57 +01:00
|
|
|
|
2022-06-06 10:59:26 +02:00
|
|
|
const std::string& getDefaultClass(const MWWorld::Store<ESM::Class>& classes)
|
2021-03-23 21:07:57 +01:00
|
|
|
{
|
|
|
|
auto it = classes.begin();
|
|
|
|
if (it != classes.end())
|
2022-06-06 10:59:26 +02:00
|
|
|
return it->mId;
|
|
|
|
throw std::runtime_error("List of NPC classes is empty!");
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<ESM::NPC> getNPCsToReplace(const MWWorld::Store<ESM::Faction>& factions, const MWWorld::Store<ESM::Class>& classes, const std::unordered_map<std::string, ESM::NPC, Misc::StringUtils::CiHash, Misc::StringUtils::CiEqual>& npcs)
|
|
|
|
{
|
|
|
|
// Cache first class from store - we will use it if current class is not found
|
|
|
|
const std::string& defaultCls = getDefaultClass(classes);
|
2021-03-23 21:07:57 +01:00
|
|
|
|
|
|
|
// Validate NPCs for non-existing class and faction.
|
|
|
|
// We will replace invalid entries by fixed ones
|
|
|
|
std::vector<ESM::NPC> npcsToReplace;
|
|
|
|
|
2021-05-15 19:50:01 +02:00
|
|
|
for (const auto& npcIter : npcs)
|
2021-03-23 21:07:57 +01:00
|
|
|
{
|
2021-04-17 10:53:47 +04:00
|
|
|
ESM::NPC npc = npcIter.second;
|
2021-03-23 21:07:57 +01:00
|
|
|
bool changed = false;
|
|
|
|
|
2022-06-06 10:59:26 +02:00
|
|
|
const std::string& npcFaction = npc.mFaction;
|
2021-03-23 21:07:57 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-06 10:59:26 +02:00
|
|
|
const std::string& npcClass = npc.mClass;
|
|
|
|
const ESM::Class *cls = classes.search(npcClass);
|
|
|
|
if (!cls)
|
2021-03-23 21:07:57 +01:00
|
|
|
{
|
2022-06-06 10:59:26 +02:00
|
|
|
Log(Debug::Verbose) << "NPC '" << npc.mId << "' (" << npc.mName << ") has nonexistent class '" << npc.mClass << "', using '" << defaultCls << "' class as replacement.";
|
|
|
|
npc.mClass = defaultCls;
|
|
|
|
changed = true;
|
2021-03-23 21:07:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (changed)
|
|
|
|
npcsToReplace.push_back(npc);
|
|
|
|
}
|
|
|
|
|
|
|
|
return npcsToReplace;
|
|
|
|
}
|
2021-07-12 20:51:13 +02:00
|
|
|
|
|
|
|
// Custom enchanted items can reference scripts that no longer exist, this doesn't necessarily mean the base item no longer exists however.
|
|
|
|
// So instead of removing the item altogether, we're only removing the script.
|
2021-10-30 17:27:57 +00:00
|
|
|
template<class MapT>
|
|
|
|
void removeMissingScripts(const MWWorld::Store<ESM::Script>& scripts, MapT& items)
|
2021-07-12 20:51:13 +02:00
|
|
|
{
|
|
|
|
for(auto& [id, item] : items)
|
|
|
|
{
|
|
|
|
if(!item.mScript.empty() && !scripts.search(item.mScript))
|
|
|
|
{
|
|
|
|
item.mScript.clear();
|
|
|
|
Log(Debug::Verbose) << "Item '" << id << "' (" << item.mName << ") has nonexistent script '" << item.mScript << "', ignoring it.";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-07-23 16:41:42 +02:00
|
|
|
|
2022-07-23 18:45:30 +02:00
|
|
|
|
2020-07-26 11:07:18 +02:00
|
|
|
}
|
|
|
|
|
2022-07-23 16:41:42 +02:00
|
|
|
template <auto Start, auto End, auto Inc, class F>
|
|
|
|
constexpr void constexpr_for(F&& f)
|
|
|
|
{
|
|
|
|
if constexpr (Start < End)
|
|
|
|
{
|
|
|
|
f(std::integral_constant<decltype(Start), Start>());
|
|
|
|
constexpr_for<Start + Inc, End, Inc>(f);
|
|
|
|
}
|
|
|
|
}
|
2022-07-03 18:11:20 +02:00
|
|
|
|
|
|
|
|
2012-10-01 19:17:04 +04:00
|
|
|
namespace MWWorld
|
2010-05-17 17:35:42 +02:00
|
|
|
{
|
2022-07-20 19:28:06 +02:00
|
|
|
using IDMap = std::unordered_map<std::string, int, Misc::StringUtils::CiHash, Misc::StringUtils::CiEqual>;
|
|
|
|
|
|
|
|
|
2022-07-03 11:47:46 +02:00
|
|
|
struct ESMStoreImp
|
|
|
|
{
|
2022-09-03 11:08:23 +02:00
|
|
|
std::tuple <
|
|
|
|
Store<ESM::Activator>,
|
|
|
|
Store<ESM::Potion>,
|
|
|
|
Store<ESM::Apparatus>,
|
|
|
|
Store<ESM::Armor>,
|
|
|
|
Store<ESM::BodyPart>,
|
|
|
|
Store<ESM::Book>,
|
|
|
|
Store<ESM::BirthSign>,
|
|
|
|
Store<ESM::Class>,
|
|
|
|
Store<ESM::Clothing>,
|
|
|
|
Store<ESM::Container>,
|
|
|
|
Store<ESM::Creature>,
|
|
|
|
Store<ESM::Dialogue>,
|
|
|
|
Store<ESM::Door>,
|
|
|
|
Store<ESM::Enchantment>,
|
|
|
|
Store<ESM::Faction>,
|
|
|
|
Store<ESM::Global>,
|
|
|
|
Store<ESM::Ingredient>,
|
|
|
|
Store<ESM::CreatureLevList>,
|
|
|
|
Store<ESM::ItemLevList>,
|
|
|
|
Store<ESM::Light>,
|
|
|
|
Store<ESM::Lockpick>,
|
|
|
|
Store<ESM::Miscellaneous>,
|
|
|
|
Store<ESM::NPC>,
|
|
|
|
Store<ESM::Probe>,
|
|
|
|
Store<ESM::Race>,
|
|
|
|
Store<ESM::Region>,
|
|
|
|
Store<ESM::Repair>,
|
|
|
|
Store<ESM::SoundGenerator>,
|
|
|
|
Store<ESM::Sound>,
|
|
|
|
Store<ESM::Spell>,
|
|
|
|
Store<ESM::StartScript>,
|
|
|
|
Store<ESM::Static>,
|
|
|
|
Store<ESM::Weapon>,
|
|
|
|
Store<ESM::GameSetting>,
|
|
|
|
Store<ESM::Script>,
|
|
|
|
|
|
|
|
// Lists that need special rules
|
|
|
|
Store<ESM::Cell>,
|
|
|
|
Store<ESM::Land>,
|
|
|
|
Store<ESM::LandTexture>,
|
|
|
|
Store<ESM::Pathgrid>,
|
|
|
|
|
|
|
|
Store<ESM::MagicEffect>,
|
|
|
|
Store<ESM::Skill>,
|
|
|
|
|
|
|
|
// Special entry which is hardcoded and not loaded from an ESM
|
|
|
|
Store<ESM::Attribute >> mStores;
|
2022-07-03 18:11:20 +02:00
|
|
|
|
2022-09-02 23:46:37 +02:00
|
|
|
std::map<ESM::RecNameInts, DynamicStore*> mRecNameToStore;
|
|
|
|
std::unordered_map<const DynamicStore*, ESM::RecNameInts> mStoreToRecName;
|
2022-07-03 18:11:20 +02:00
|
|
|
|
2022-07-20 19:28:06 +02:00
|
|
|
// Lookup of all IDs. Makes looking up references faster. Just
|
|
|
|
// maps the id name to the record type.
|
|
|
|
IDMap mIds;
|
|
|
|
IDMap mStaticIds;
|
|
|
|
|
2022-07-03 13:17:55 +02:00
|
|
|
|
|
|
|
template<typename T>
|
2022-07-19 17:58:21 +02:00
|
|
|
static const T* esm3StoreInsert(ESMStore& stores, const T &toInsert)
|
2022-07-03 13:17:55 +02:00
|
|
|
{
|
|
|
|
const std::string id = "$dynamic" + std::to_string(stores.mDynamicCount++);
|
|
|
|
|
2022-07-19 17:58:21 +02:00
|
|
|
Store<T> &store = stores.getWritable<T>();
|
2022-07-03 13:17:55 +02:00
|
|
|
if (store.search(id) != nullptr)
|
|
|
|
{
|
|
|
|
const std::string msg = "Try to override existing record '" + id + "'";
|
|
|
|
throw std::runtime_error(msg);
|
|
|
|
}
|
|
|
|
T record = toInsert;
|
|
|
|
|
|
|
|
record.mId = id;
|
|
|
|
|
|
|
|
T *ptr = store.insert(record);
|
2022-09-02 23:39:38 +02:00
|
|
|
auto esm3RecordType_find = stores.mStoreImp->mStoreToRecName.find(&stores.get<T>());
|
2022-07-03 18:11:20 +02:00
|
|
|
|
2022-09-02 23:39:38 +02:00
|
|
|
if (esm3RecordType_find != stores.mStoreImp->mStoreToRecName.end())
|
2022-07-03 18:11:20 +02:00
|
|
|
{
|
2022-07-20 19:28:06 +02:00
|
|
|
stores.mStoreImp->mIds[ptr->mId] = esm3RecordType_find->second;
|
2022-07-03 13:17:55 +02:00
|
|
|
}
|
|
|
|
return ptr;
|
|
|
|
}
|
2022-07-03 13:46:12 +02:00
|
|
|
|
|
|
|
template <class T>
|
2022-07-19 17:58:21 +02:00
|
|
|
static const T * esm3overrideRecord(ESMStore& stores, const T &x) {
|
|
|
|
Store<T> &store = stores.getWritable<T>();
|
2022-07-03 13:46:12 +02:00
|
|
|
|
|
|
|
T *ptr = store.insert(x);
|
2022-09-02 23:39:38 +02:00
|
|
|
auto esm3RecordType_find = stores.mStoreImp->mStoreToRecName.find(&stores.get<T>());
|
|
|
|
if (esm3RecordType_find != stores.mStoreImp->mStoreToRecName.end())
|
2022-07-03 18:11:20 +02:00
|
|
|
{
|
2022-07-20 19:28:06 +02:00
|
|
|
stores.mStoreImp->mIds[ptr->mId] = esm3RecordType_find->second;
|
2022-07-03 13:46:12 +02:00
|
|
|
}
|
|
|
|
return ptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
template <class T>
|
2022-07-19 17:58:21 +02:00
|
|
|
static const T *esm3insertStatic(ESMStore& stores, const T &x)
|
2022-07-03 13:46:12 +02:00
|
|
|
{
|
|
|
|
const std::string id = "$dynamic" + std::to_string(stores.mDynamicCount++);
|
|
|
|
|
2022-07-19 17:58:21 +02:00
|
|
|
Store<T> &store = stores.getWritable<T>();
|
2022-07-03 13:46:12 +02:00
|
|
|
if (store.search(id) != nullptr)
|
|
|
|
{
|
|
|
|
const std::string msg = "Try to override existing record '" + id + "'";
|
|
|
|
throw std::runtime_error(msg);
|
|
|
|
}
|
|
|
|
T record = x;
|
|
|
|
|
|
|
|
T *ptr = store.insertStatic(record);
|
2022-09-02 23:39:38 +02:00
|
|
|
auto esm3RecordType_find = stores.mStoreImp->mStoreToRecName.find(&stores.get<T>());
|
|
|
|
if (esm3RecordType_find != stores.mStoreImp->mStoreToRecName.end())
|
2022-07-03 18:11:20 +02:00
|
|
|
{
|
2022-07-20 19:28:06 +02:00
|
|
|
stores.mStoreImp->mIds[ptr->mId] = esm3RecordType_find->second;
|
2022-07-03 13:46:12 +02:00
|
|
|
}
|
|
|
|
return ptr;
|
|
|
|
}
|
2022-07-03 18:11:20 +02:00
|
|
|
|
2022-09-03 11:08:23 +02:00
|
|
|
template<typename T>
|
|
|
|
static int AssignStoreToIndex(ESMStore& stores, Store<T>& store)
|
2022-07-03 18:11:20 +02:00
|
|
|
{
|
2022-09-03 11:08:23 +02:00
|
|
|
const int storeIndex = ESMStore::getTypeIndex<T>();
|
|
|
|
assert(ESMStore::getTypeIndex<T>() == storeIndex);
|
2022-09-03 12:09:12 +02:00
|
|
|
const std::size_t index_t(storeIndex);
|
2022-09-03 11:47:38 +02:00
|
|
|
if (stores.mStores.size() <= index_t)
|
|
|
|
stores.mStores.resize(index_t + 1);
|
2022-07-19 17:58:21 +02:00
|
|
|
|
2022-09-03 11:08:23 +02:00
|
|
|
assert(&store == &std::get<Store<T>>(stores.mStoreImp->mStores));
|
|
|
|
|
|
|
|
stores.mStores[storeIndex] = &store;
|
2022-09-02 23:46:37 +02:00
|
|
|
if constexpr (std::is_convertible<Store<T>*, DynamicStore*>::value)
|
2022-07-23 16:41:42 +02:00
|
|
|
{
|
2022-09-03 11:08:23 +02:00
|
|
|
stores.mDynamicStores.push_back(&store);
|
2022-09-02 23:36:41 +02:00
|
|
|
constexpr ESM::RecNameInts recName = T::sRecordId;
|
2022-07-24 12:37:27 +02:00
|
|
|
if constexpr (recName != ESM::REC_INTERNAL_PLAYER)
|
|
|
|
{
|
2022-09-03 11:08:23 +02:00
|
|
|
stores.mStoreImp->mRecNameToStore[recName] = &store;
|
2022-07-24 12:37:27 +02:00
|
|
|
}
|
|
|
|
}
|
2022-09-03 11:08:23 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
ESMStoreImp(ESMStore& store)
|
|
|
|
{
|
|
|
|
|
2022-07-24 12:37:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void SetupAfterStoresCreation(ESMStore& store)
|
|
|
|
{
|
2022-09-02 23:39:38 +02:00
|
|
|
for (const auto& recordStorePair : mRecNameToStore)
|
2022-07-24 12:37:27 +02:00
|
|
|
{
|
2022-09-02 23:46:37 +02:00
|
|
|
const DynamicStore* storePtr = recordStorePair.second;
|
2022-09-02 23:39:38 +02:00
|
|
|
mStoreToRecName[storePtr] = recordStorePair.first;
|
2022-07-23 16:41:42 +02:00
|
|
|
}
|
2022-07-03 18:11:20 +02:00
|
|
|
}
|
2022-07-03 11:47:46 +02:00
|
|
|
};
|
|
|
|
|
2022-07-03 18:11:20 +02:00
|
|
|
|
2022-07-20 19:28:06 +02:00
|
|
|
int ESMStore::find(const std::string& id) const
|
|
|
|
{
|
|
|
|
|
|
|
|
IDMap::const_iterator it = mStoreImp->mIds.find(id);
|
|
|
|
if (it == mStoreImp->mIds.end()) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return it->second;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
int ESMStore::findStatic(const std::string& id) const
|
|
|
|
{
|
|
|
|
IDMap::const_iterator it = mStoreImp-> mStaticIds.find(id);
|
|
|
|
if (it == mStoreImp->mStaticIds.end()) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return it->second;
|
|
|
|
}
|
|
|
|
|
2022-07-03 11:47:46 +02:00
|
|
|
ESMStore::ESMStore()
|
|
|
|
{
|
2022-07-24 12:37:27 +02:00
|
|
|
mStoreImp = std::make_unique<ESMStoreImp>(*this);
|
2022-09-03 11:08:23 +02:00
|
|
|
std::apply([this](auto& ...x){std::make_tuple(ESMStoreImp::AssignStoreToIndex(*this, x)...);} , mStoreImp->mStores);
|
2022-07-03 11:47:46 +02:00
|
|
|
mDynamicCount = 0;
|
2022-07-24 12:37:27 +02:00
|
|
|
mStoreImp->SetupAfterStoresCreation(*this);
|
2022-07-03 18:11:20 +02:00
|
|
|
getWritable<ESM::Pathgrid>().setCells(getWritable<ESM::Cell>());
|
2022-07-03 11:47:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
ESMStore::~ESMStore()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2022-07-03 14:40:37 +02:00
|
|
|
void ESMStore::clearDynamic()
|
|
|
|
{
|
2022-09-03 11:08:23 +02:00
|
|
|
for (const auto& store : mDynamicStores)
|
2022-07-19 17:58:21 +02:00
|
|
|
store->clearDynamic();
|
2022-07-03 14:40:37 +02:00
|
|
|
|
|
|
|
movePlayerRecord();
|
|
|
|
}
|
|
|
|
|
2012-11-05 18:09:14 +04:00
|
|
|
static bool isCacheableRecord(int id)
|
|
|
|
{
|
|
|
|
if (id == ESM::REC_ACTI || id == ESM::REC_ALCH || id == ESM::REC_APPA || id == ESM::REC_ARMO ||
|
|
|
|
id == ESM::REC_BOOK || id == ESM::REC_CLOT || id == ESM::REC_CONT || id == ESM::REC_CREA ||
|
|
|
|
id == ESM::REC_DOOR || id == ESM::REC_INGR || id == ESM::REC_LEVC || id == ESM::REC_LEVI ||
|
|
|
|
id == ESM::REC_LIGH || id == ESM::REC_LOCK || id == ESM::REC_MISC || id == ESM::REC_NPC_ ||
|
2016-01-02 00:49:53 +01:00
|
|
|
id == ESM::REC_PROB || id == ESM::REC_REPA || id == ESM::REC_STAT || id == ESM::REC_WEAP ||
|
|
|
|
id == ESM::REC_BODY)
|
2012-11-05 18:09:14 +04:00
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-04-16 16:28:39 +02:00
|
|
|
void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener, ESM::Dialogue*& dialogue)
|
2010-05-17 17:35:42 +02:00
|
|
|
{
|
2022-06-03 18:59:08 +02:00
|
|
|
if (listener != nullptr)
|
|
|
|
listener->setProgressRange(::EsmLoader::fileProgress);
|
2013-08-27 15:48:13 +02:00
|
|
|
|
2015-11-27 21:40:36 +01:00
|
|
|
// Land texture loading needs to use a separate internal store for each plugin.
|
2021-11-03 08:02:06 +00:00
|
|
|
// We set the number of plugins here so we can properly verify if valid plugin
|
|
|
|
// indices are being passed to the LandTexture Store retrieval methods.
|
2022-07-03 18:11:20 +02:00
|
|
|
getWritable<ESM::LandTexture>().resize(esm.getIndex()+1);
|
2012-11-25 17:19:29 +01:00
|
|
|
|
2010-08-06 15:19:39 +02:00
|
|
|
// Loop through all records
|
|
|
|
while(esm.hasMoreRecs())
|
2010-05-17 17:35:42 +02:00
|
|
|
{
|
2012-10-01 19:17:04 +04:00
|
|
|
ESM::NAME n = esm.getRecName();
|
2010-08-06 15:19:39 +02:00
|
|
|
esm.getRecHeader();
|
2022-04-16 16:28:39 +02:00
|
|
|
if (esm.getRecordFlags() & ESM::FLAG_Ignored)
|
|
|
|
{
|
|
|
|
esm.skipRecord();
|
|
|
|
continue;
|
|
|
|
}
|
2010-05-17 17:35:42 +02:00
|
|
|
|
2010-08-06 15:19:39 +02:00
|
|
|
// Look up the record type.
|
2022-07-20 19:04:31 +02:00
|
|
|
ESM::RecNameInts recName = static_cast<ESM::RecNameInts>(n.toInt());
|
2022-09-02 23:39:38 +02:00
|
|
|
const auto& it = mStoreImp->mRecNameToStore.find(recName);
|
2012-11-05 18:09:14 +04:00
|
|
|
|
2022-09-02 23:39:38 +02:00
|
|
|
if (it == mStoreImp->mRecNameToStore.end()) {
|
2022-07-20 19:04:31 +02:00
|
|
|
if (recName == ESM::REC_INFO) {
|
2014-05-31 00:36:37 +02:00
|
|
|
if (dialogue)
|
|
|
|
{
|
|
|
|
dialogue->readInfo(esm, esm.getIndex() != 0);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2018-08-14 23:05:43 +04:00
|
|
|
Log(Debug::Error) << "Error: info record without dialog";
|
2010-08-06 15:19:39 +02:00
|
|
|
esm.skipRecord();
|
|
|
|
}
|
2021-10-17 02:52:22 +02:00
|
|
|
} else if (n.toInt() == ESM::REC_MGEF) {
|
2022-07-03 18:11:20 +02:00
|
|
|
getWritable<ESM::MagicEffect>().load (esm);
|
2021-10-17 02:52:22 +02:00
|
|
|
} else if (n.toInt() == ESM::REC_SKIL) {
|
2022-07-03 18:11:20 +02:00
|
|
|
getWritable<ESM::Skill>().load (esm);
|
2014-09-13 20:48:24 +02:00
|
|
|
}
|
2021-10-17 02:52:22 +02:00
|
|
|
else if (n.toInt() == ESM::REC_FILT || n.toInt() == ESM::REC_DBGP)
|
2014-09-13 20:48:24 +02:00
|
|
|
{
|
|
|
|
// ignore project file only records
|
|
|
|
esm.skipRecord();
|
|
|
|
}
|
2021-09-26 15:21:46 +02:00
|
|
|
else if (n.toInt() == ESM::REC_LUAL)
|
|
|
|
{
|
|
|
|
ESM::LuaScriptsCfg cfg;
|
|
|
|
cfg.load(esm);
|
2022-05-20 21:47:13 +02:00
|
|
|
cfg.adjustRefNums(esm);
|
2021-09-26 15:21:46 +02:00
|
|
|
mLuaContent.push_back(std::move(cfg));
|
|
|
|
}
|
2014-09-13 20:48:24 +02:00
|
|
|
else {
|
2021-06-10 12:55:18 +02:00
|
|
|
throw std::runtime_error("Unknown record: " + n.toString());
|
2010-08-06 15:19:39 +02:00
|
|
|
}
|
2012-11-05 18:09:14 +04:00
|
|
|
} else {
|
2022-07-20 19:04:31 +02:00
|
|
|
RecordId id = it->second->load(esm);
|
2015-07-12 15:20:22 +03:00
|
|
|
if (id.mIsDeleted)
|
2015-07-08 21:26:20 +03:00
|
|
|
{
|
2022-07-20 19:04:31 +02:00
|
|
|
it->second->eraseStatic(id.mId);
|
2015-07-08 21:26:20 +03:00
|
|
|
continue;
|
2014-06-07 19:21:37 +02:00
|
|
|
}
|
|
|
|
|
2021-10-17 02:52:22 +02:00
|
|
|
if (n.toInt() == ESM::REC_DIAL) {
|
2022-07-03 18:11:20 +02:00
|
|
|
dialogue = const_cast<ESM::Dialogue*>(getWritable<ESM::Dialogue>().find(id.mId));
|
2012-11-05 18:09:14 +04:00
|
|
|
} else {
|
2020-11-13 11:39:47 +04:00
|
|
|
dialogue = nullptr;
|
2012-11-05 18:09:14 +04:00
|
|
|
}
|
2010-08-06 15:19:39 +02:00
|
|
|
}
|
2022-06-03 18:59:08 +02:00
|
|
|
if (listener != nullptr)
|
|
|
|
listener->setProgress(::EsmLoader::fileProgress * esm.getFileOffset() / esm.getFileSize());
|
2010-05-17 17:35:42 +02:00
|
|
|
}
|
|
|
|
}
|
2012-10-01 19:17:04 +04:00
|
|
|
|
2022-09-03 11:08:23 +02:00
|
|
|
static int sTypeIndexCounter = 0;
|
|
|
|
|
|
|
|
int& ESMStore::getTypeIndexCounter()
|
|
|
|
{
|
|
|
|
return sTypeIndexCounter;
|
|
|
|
}
|
|
|
|
|
2021-09-26 15:21:46 +02:00
|
|
|
ESM::LuaScriptsCfg ESMStore::getLuaScriptsCfg() const
|
|
|
|
{
|
|
|
|
ESM::LuaScriptsCfg cfg;
|
|
|
|
for (const LuaContent& c : mLuaContent)
|
|
|
|
{
|
|
|
|
if (std::holds_alternative<std::string>(c))
|
|
|
|
{
|
|
|
|
// *.omwscripts are intentionally reloaded every time when `getLuaScriptsCfg` is called.
|
|
|
|
// It is important for the `reloadlua` console command.
|
|
|
|
try
|
|
|
|
{
|
|
|
|
auto file = std::ifstream(std::get<std::string>(c));
|
|
|
|
std::string fileContent(std::istreambuf_iterator<char>(file), {});
|
|
|
|
LuaUtil::parseOMWScripts(cfg, fileContent);
|
|
|
|
}
|
|
|
|
catch (std::exception& e) { Log(Debug::Error) << e.what(); }
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
const ESM::LuaScriptsCfg& addition = std::get<ESM::LuaScriptsCfg>(c);
|
|
|
|
cfg.mScripts.insert(cfg.mScripts.end(), addition.mScripts.begin(), addition.mScripts.end());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return cfg;
|
|
|
|
}
|
|
|
|
|
2022-06-01 22:53:18 +02:00
|
|
|
void ESMStore::setUp()
|
2012-11-05 18:09:14 +04:00
|
|
|
{
|
2022-07-20 19:28:06 +02:00
|
|
|
mStoreImp->mIds.clear();
|
2015-12-11 15:55:45 +01:00
|
|
|
|
2022-09-02 23:46:37 +02:00
|
|
|
std::map<ESM::RecNameInts, DynamicStore*>::iterator storeIt = mStoreImp->mRecNameToStore.begin();
|
2022-09-02 23:39:38 +02:00
|
|
|
for (; storeIt != mStoreImp->mRecNameToStore.end(); ++storeIt) {
|
2022-07-20 19:04:31 +02:00
|
|
|
storeIt->second->setUp();
|
2015-12-11 16:59:13 +01:00
|
|
|
|
2015-12-11 15:55:45 +01:00
|
|
|
if (isCacheableRecord(storeIt->first))
|
|
|
|
{
|
|
|
|
std::vector<std::string> identifiers;
|
2022-07-20 19:04:31 +02:00
|
|
|
storeIt->second->listIdentifier(identifiers);
|
2015-12-11 15:55:45 +01:00
|
|
|
|
|
|
|
for (std::vector<std::string>::const_iterator record = identifiers.begin(); record != identifiers.end(); ++record)
|
2022-07-20 19:28:06 +02:00
|
|
|
mStoreImp->mIds[*record] = storeIt->first;
|
2015-12-11 15:55:45 +01:00
|
|
|
}
|
2012-11-05 18:09:14 +04:00
|
|
|
}
|
2019-06-13 13:37:00 +00:00
|
|
|
|
2022-07-20 19:28:06 +02:00
|
|
|
if (mStoreImp->mStaticIds.empty())
|
|
|
|
for (const auto& [k, v] : mStoreImp->mIds)
|
|
|
|
mStoreImp->mStaticIds.emplace(Misc::StringUtils::lowerCase(k), v);
|
2019-06-13 13:37:00 +00:00
|
|
|
|
2022-07-03 18:11:20 +02:00
|
|
|
getWritable<ESM::Skill>().setUp();
|
|
|
|
getWritable<ESM::MagicEffect>().setUp();;
|
|
|
|
getWritable<ESM::Attribute>().setUp();
|
|
|
|
getWritable<ESM::Dialogue>().setUp();
|
2022-06-01 22:53:18 +02:00
|
|
|
}
|
2018-06-09 20:47:17 +04:00
|
|
|
|
2022-06-01 22:53:18 +02:00
|
|
|
void ESMStore::validateRecords(ESM::ReadersCache& readers)
|
|
|
|
{
|
|
|
|
validate();
|
|
|
|
countAllCellRefs(readers);
|
2020-07-26 11:07:18 +02:00
|
|
|
}
|
|
|
|
|
2022-06-01 22:53:18 +02:00
|
|
|
void ESMStore::countAllCellRefs(ESM::ReadersCache& readers)
|
2020-07-26 11:07:18 +02:00
|
|
|
{
|
2021-11-02 13:46:41 +00:00
|
|
|
// TODO: We currently need to read entire files here again.
|
|
|
|
// We should consider consolidating or deferring this reading.
|
2020-07-26 11:07:18 +02:00
|
|
|
if(!mRefCount.empty())
|
|
|
|
return;
|
2021-04-06 23:59:58 +02:00
|
|
|
std::vector<Ref> refs;
|
|
|
|
std::vector<std::string> refIDs;
|
2022-07-03 18:11:20 +02:00
|
|
|
Store<ESM::Cell> Cells = getWritable < ESM::Cell>();
|
|
|
|
for(auto it = Cells.intBegin(); it != Cells.intEnd(); ++it)
|
2021-04-06 23:59:58 +02:00
|
|
|
readRefs(*it, refs, refIDs, readers);
|
2022-07-03 18:11:20 +02:00
|
|
|
for(auto it = Cells.extBegin(); it != Cells.extEnd(); ++it)
|
2021-04-06 23:59:58 +02:00
|
|
|
readRefs(*it, refs, refIDs, readers);
|
|
|
|
const auto lessByRefNum = [] (const Ref& l, const Ref& r) { return l.mRefNum < r.mRefNum; };
|
|
|
|
std::stable_sort(refs.begin(), refs.end(), lessByRefNum);
|
|
|
|
const auto equalByRefNum = [] (const Ref& l, const Ref& r) { return l.mRefNum == r.mRefNum; };
|
|
|
|
const auto incrementRefCount = [&] (const Ref& value)
|
|
|
|
{
|
|
|
|
if (value.mRefID != deletedRefID)
|
|
|
|
{
|
|
|
|
std::string& refId = refIDs[value.mRefID];
|
2021-11-02 13:46:41 +00:00
|
|
|
// We manually lower case IDs here for the time being to improve performance.
|
|
|
|
Misc::StringUtils::lowerCaseInPlace(refId);
|
2021-04-06 23:59:58 +02:00
|
|
|
++mRefCount[std::move(refId)];
|
|
|
|
}
|
|
|
|
};
|
|
|
|
Misc::forEachUnique(refs.rbegin(), refs.rend(), equalByRefNum, incrementRefCount);
|
2020-07-26 11:07:18 +02:00
|
|
|
}
|
|
|
|
|
2022-06-13 18:21:29 +02:00
|
|
|
int ESMStore::getRefCount(std::string_view id) const
|
2020-07-26 11:07:18 +02:00
|
|
|
{
|
2021-11-02 13:46:41 +00:00
|
|
|
const std::string lowerId = Misc::StringUtils::lowerCase(id);
|
|
|
|
auto it = mRefCount.find(lowerId);
|
2020-07-26 11:07:18 +02:00
|
|
|
if(it == mRefCount.end())
|
|
|
|
return 0;
|
|
|
|
return it->second;
|
2018-06-09 20:47:17 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
void ESMStore::validate()
|
|
|
|
{
|
2022-07-19 17:58:21 +02:00
|
|
|
auto& npcs = getWritable<ESM::NPC>();
|
|
|
|
std::vector<ESM::NPC> npcsToReplace = getNPCsToReplace(getWritable<ESM::Faction>(), getWritable<ESM::Class>(), npcs.mStatic);
|
2018-06-09 20:47:17 +04:00
|
|
|
|
2019-03-31 18:54:12 +03:00
|
|
|
for (const ESM::NPC &npc : npcsToReplace)
|
2018-06-09 20:47:17 +04:00
|
|
|
{
|
2022-07-19 17:58:21 +02:00
|
|
|
npcs.eraseStatic(npc.mId);
|
|
|
|
npcs.insertStatic(npc);
|
2018-06-09 20:47:17 +04:00
|
|
|
}
|
2019-03-31 18:54:12 +03:00
|
|
|
|
|
|
|
// Validate spell effects for invalid arguments
|
|
|
|
std::vector<ESM::Spell> spellsToReplace;
|
2022-07-03 18:11:20 +02:00
|
|
|
auto& Spells = getWritable<ESM::Spell>();
|
|
|
|
for (ESM::Spell spell : Spells)
|
2019-03-31 18:54:12 +03:00
|
|
|
{
|
|
|
|
if (spell.mEffects.mList.empty())
|
|
|
|
continue;
|
|
|
|
|
|
|
|
bool changed = false;
|
2019-05-31 11:23:58 +03:00
|
|
|
auto iter = spell.mEffects.mList.begin();
|
|
|
|
while (iter != spell.mEffects.mList.end())
|
2019-03-31 18:54:12 +03:00
|
|
|
{
|
2022-07-03 18:11:20 +02:00
|
|
|
const ESM::MagicEffect* mgef = getWritable<ESM::MagicEffect>().search(iter->mEffectID);
|
2019-05-31 11:23:58 +03:00
|
|
|
if (!mgef)
|
|
|
|
{
|
2019-10-17 23:45:27 +03:00
|
|
|
Log(Debug::Verbose) << "Spell '" << spell.mId << "' has an invalid effect (index " << iter->mEffectID << ") present. Dropping the effect.";
|
2019-05-31 11:23:58 +03:00
|
|
|
iter = spell.mEffects.mList.erase(iter);
|
|
|
|
changed = true;
|
2019-03-31 18:54:12 +03:00
|
|
|
continue;
|
2019-05-31 11:23:58 +03:00
|
|
|
}
|
2019-03-31 18:54:12 +03:00
|
|
|
|
|
|
|
if (mgef->mData.mFlags & ESM::MagicEffect::TargetSkill)
|
|
|
|
{
|
2019-05-31 11:23:58 +03:00
|
|
|
if (iter->mAttribute != -1)
|
2019-03-31 18:54:12 +03:00
|
|
|
{
|
2019-05-31 11:23:58 +03:00
|
|
|
iter->mAttribute = -1;
|
|
|
|
Log(Debug::Verbose) << ESM::MagicEffect::effectIdToString(iter->mEffectID) <<
|
2019-10-17 23:45:27 +03:00
|
|
|
" effect of spell '" << spell.mId << "' has an attribute argument present. Dropping the argument.";
|
2019-03-31 18:54:12 +03:00
|
|
|
changed = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (mgef->mData.mFlags & ESM::MagicEffect::TargetAttribute)
|
|
|
|
{
|
2019-05-31 11:23:58 +03:00
|
|
|
if (iter->mSkill != -1)
|
2019-03-31 18:54:12 +03:00
|
|
|
{
|
2019-05-31 11:23:58 +03:00
|
|
|
iter->mSkill = -1;
|
|
|
|
Log(Debug::Verbose) << ESM::MagicEffect::effectIdToString(iter->mEffectID) <<
|
2019-10-17 23:45:27 +03:00
|
|
|
" effect of spell '" << spell.mId << "' has a skill argument present. Dropping the argument.";
|
2019-03-31 18:54:12 +03:00
|
|
|
changed = true;
|
|
|
|
}
|
|
|
|
}
|
2019-05-31 11:23:58 +03:00
|
|
|
else if (iter->mSkill != -1 || iter->mAttribute != -1)
|
2019-03-31 18:54:12 +03:00
|
|
|
{
|
2019-05-31 11:23:58 +03:00
|
|
|
iter->mSkill = -1;
|
|
|
|
iter->mAttribute = -1;
|
|
|
|
Log(Debug::Verbose) << ESM::MagicEffect::effectIdToString(iter->mEffectID) <<
|
2019-10-17 23:45:27 +03:00
|
|
|
" effect of spell '" << spell.mId << "' has argument(s) present. Dropping the argument(s).";
|
2019-03-31 18:54:12 +03:00
|
|
|
changed = true;
|
|
|
|
}
|
2019-05-31 11:23:58 +03:00
|
|
|
|
|
|
|
++iter;
|
2019-03-31 18:54:12 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (changed)
|
|
|
|
spellsToReplace.emplace_back(spell);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const ESM::Spell &spell : spellsToReplace)
|
|
|
|
{
|
2022-07-03 18:11:20 +02:00
|
|
|
Spells.eraseStatic(spell.mId);
|
|
|
|
Spells.insertStatic(spell);
|
2019-03-31 18:54:12 +03:00
|
|
|
}
|
2012-11-05 18:09:14 +04:00
|
|
|
}
|
|
|
|
|
2022-07-03 11:47:46 +02:00
|
|
|
void ESMStore::movePlayerRecord()
|
|
|
|
{
|
2022-07-19 17:58:21 +02:00
|
|
|
auto& npcs = getWritable<ESM::NPC>();
|
|
|
|
auto player = npcs.find("player");
|
|
|
|
npcs.insert(*player);
|
2022-07-03 11:47:46 +02:00
|
|
|
}
|
|
|
|
|
2021-03-23 21:07:57 +01:00
|
|
|
void ESMStore::validateDynamic()
|
|
|
|
{
|
2022-07-19 17:58:21 +02:00
|
|
|
auto& npcs = getWritable<ESM::NPC>();
|
2022-07-03 18:11:20 +02:00
|
|
|
auto& scripts = getWritable<ESM::Script>();
|
|
|
|
|
2022-07-19 17:58:21 +02:00
|
|
|
std::vector<ESM::NPC> npcsToReplace = getNPCsToReplace(getWritable<ESM::Faction>(), getWritable<ESM::Class>(), npcs.mDynamic);
|
2021-03-23 21:07:57 +01:00
|
|
|
|
|
|
|
for (const ESM::NPC &npc : npcsToReplace)
|
2022-07-19 17:58:21 +02:00
|
|
|
npcs.insert(npc);
|
2021-07-12 20:51:13 +02:00
|
|
|
|
2022-07-03 18:11:20 +02:00
|
|
|
removeMissingScripts(scripts, getWritable<ESM::Armor>().mDynamic);
|
|
|
|
removeMissingScripts(scripts, getWritable<ESM::Book>().mDynamic);
|
|
|
|
removeMissingScripts(scripts, getWritable<ESM::Clothing>().mDynamic);
|
|
|
|
removeMissingScripts(scripts, getWritable<ESM::Weapon>().mDynamic);
|
2021-07-12 20:51:13 +02:00
|
|
|
|
2022-07-03 18:11:20 +02:00
|
|
|
removeMissingObjects(getWritable<ESM::CreatureLevList>());
|
|
|
|
removeMissingObjects(getWritable<ESM::ItemLevList>());
|
2021-07-12 20:51:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Leveled lists can be modified by scripts. This removes items that no longer exist (presumably because the plugin was removed) from modified lists
|
|
|
|
template<class T>
|
|
|
|
void ESMStore::removeMissingObjects(Store<T>& store)
|
|
|
|
{
|
2021-07-13 21:18:24 +02:00
|
|
|
for(auto& entry : store.mDynamic)
|
2021-07-12 20:51:13 +02:00
|
|
|
{
|
2021-07-13 21:18:24 +02:00
|
|
|
auto first = std::remove_if(entry.second.mList.begin(), entry.second.mList.end(), [&] (const auto& item)
|
2021-07-12 20:51:13 +02:00
|
|
|
{
|
|
|
|
if(!find(item.mId))
|
|
|
|
{
|
2021-07-13 21:18:24 +02:00
|
|
|
Log(Debug::Verbose) << "Leveled list '" << entry.first << "' has nonexistent object '" << item.mId << "', ignoring it.";
|
2021-07-12 20:51:13 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
});
|
2021-07-13 21:18:24 +02:00
|
|
|
entry.second.mList.erase(first, entry.second.mList.end());
|
2021-07-12 20:51:13 +02:00
|
|
|
}
|
2021-03-23 21:07:57 +01:00
|
|
|
}
|
|
|
|
|
2013-12-07 13:17:28 +01:00
|
|
|
int ESMStore::countSavedGameRecords() const
|
|
|
|
{
|
2014-05-11 00:32:22 +02:00
|
|
|
return 1 // DYNA (dynamic name counter)
|
2022-07-03 18:11:20 +02:00
|
|
|
+ get<ESM::Potion>().getDynamicSize()
|
|
|
|
+ get<ESM::Armor>().getDynamicSize()
|
|
|
|
+ get<ESM::Book>().getDynamicSize()
|
|
|
|
+ get<ESM::Class>().getDynamicSize()
|
|
|
|
+ get<ESM::Clothing>().getDynamicSize()
|
|
|
|
+ get<ESM::Enchantment>().getDynamicSize()
|
|
|
|
+ get<ESM::NPC>().getDynamicSize()
|
|
|
|
+ get<ESM::Spell>().getDynamicSize()
|
|
|
|
+ get<ESM::Weapon>().getDynamicSize()
|
|
|
|
+ get<ESM::CreatureLevList>().getDynamicSize()
|
|
|
|
+ get<ESM::ItemLevList>().getDynamicSize()
|
|
|
|
+ get<ESM::Creature>().getDynamicSize()
|
|
|
|
+ get<ESM::Container>().getDynamicSize();
|
|
|
|
|
2013-12-07 13:17:28 +01:00
|
|
|
}
|
|
|
|
|
2014-04-28 11:29:57 +02:00
|
|
|
void ESMStore::write (ESM::ESMWriter& writer, Loading::Listener& progress) const
|
2013-12-07 13:17:28 +01:00
|
|
|
{
|
2014-05-11 00:32:22 +02:00
|
|
|
writer.startRecord(ESM::REC_DYNA);
|
|
|
|
writer.startSubRecord("COUN");
|
|
|
|
writer.writeT(mDynamicCount);
|
|
|
|
writer.endRecord("COUN");
|
|
|
|
writer.endRecord(ESM::REC_DYNA);
|
|
|
|
|
2022-07-03 18:11:20 +02:00
|
|
|
get<ESM::Potion>().write (writer, progress);
|
|
|
|
get<ESM::Armor>().write (writer, progress);
|
|
|
|
get<ESM::Book>().write (writer, progress);
|
|
|
|
get<ESM::Class>().write (writer, progress);
|
|
|
|
get<ESM::Clothing>().write (writer, progress);
|
|
|
|
get<ESM::Enchantment>().write (writer, progress);
|
|
|
|
get<ESM::NPC>().write (writer, progress);
|
|
|
|
get<ESM::Spell>().write (writer, progress);
|
|
|
|
get<ESM::Weapon>().write (writer, progress);
|
|
|
|
get<ESM::CreatureLevList>().write (writer, progress);
|
|
|
|
get<ESM::ItemLevList>().write (writer, progress);
|
|
|
|
get<ESM::Creature>().write (writer, progress);
|
|
|
|
get<ESM::Container>().write (writer, progress);
|
2013-12-07 13:17:28 +01:00
|
|
|
}
|
|
|
|
|
2022-07-20 19:04:31 +02:00
|
|
|
bool ESMStore::readRecord (ESM::ESMReader& reader, uint32_t type_id)
|
2013-12-07 13:17:28 +01:00
|
|
|
{
|
2022-07-20 19:04:31 +02:00
|
|
|
ESM::RecNameInts type = (ESM::RecNameInts)type_id;
|
2013-12-07 13:17:28 +01:00
|
|
|
switch (type)
|
|
|
|
{
|
|
|
|
case ESM::REC_ALCH:
|
|
|
|
case ESM::REC_ARMO:
|
|
|
|
case ESM::REC_BOOK:
|
|
|
|
case ESM::REC_CLAS:
|
|
|
|
case ESM::REC_CLOT:
|
|
|
|
case ESM::REC_ENCH:
|
|
|
|
case ESM::REC_SPEL:
|
|
|
|
case ESM::REC_WEAP:
|
2014-12-17 01:05:32 +01:00
|
|
|
case ESM::REC_LEVI:
|
|
|
|
case ESM::REC_LEVC:
|
2022-09-02 23:39:38 +02:00
|
|
|
mStoreImp->mRecNameToStore[type]->read (reader);
|
2021-03-22 22:29:10 +01:00
|
|
|
return true;
|
|
|
|
case ESM::REC_NPC_:
|
2020-06-02 21:59:37 +02:00
|
|
|
case ESM::REC_CREA:
|
2020-10-29 13:55:24 +01:00
|
|
|
case ESM::REC_CONT:
|
2022-09-02 23:39:38 +02:00
|
|
|
mStoreImp->mRecNameToStore[type]->read (reader, true);
|
2013-12-07 13:17:28 +01:00
|
|
|
return true;
|
|
|
|
|
2014-05-11 00:32:22 +02:00
|
|
|
case ESM::REC_DYNA:
|
|
|
|
reader.getSubNameIs("COUN");
|
|
|
|
reader.getHT(mDynamicCount);
|
|
|
|
return true;
|
|
|
|
|
2013-12-07 13:17:28 +01:00
|
|
|
default:
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-02 21:59:37 +02:00
|
|
|
void ESMStore::checkPlayer()
|
|
|
|
{
|
|
|
|
setUp();
|
|
|
|
|
2022-07-03 18:11:20 +02:00
|
|
|
const ESM::NPC *player = get<ESM::NPC>().find ("player");
|
2020-06-02 21:59:37 +02:00
|
|
|
|
2022-07-03 18:11:20 +02:00
|
|
|
if (!get<ESM::Race>().find (player->mRace) ||
|
|
|
|
!get<ESM::Class>().find (player->mClass))
|
2020-06-02 21:59:37 +02:00
|
|
|
throw std::runtime_error ("Invalid player record (race or class unavailable");
|
|
|
|
}
|
|
|
|
|
2021-10-30 17:27:57 +00:00
|
|
|
std::pair<std::shared_ptr<MWMechanics::SpellList>, bool> ESMStore::getSpellList(const std::string& id) const
|
2020-07-28 08:33:28 +02:00
|
|
|
{
|
|
|
|
auto result = mSpellListCache.find(id);
|
|
|
|
std::shared_ptr<MWMechanics::SpellList> ptr;
|
|
|
|
if (result != mSpellListCache.end())
|
|
|
|
ptr = result->second.lock();
|
|
|
|
if (!ptr)
|
|
|
|
{
|
|
|
|
int type = find(id);
|
|
|
|
ptr = std::make_shared<MWMechanics::SpellList>(id, type);
|
|
|
|
if (result != mSpellListCache.end())
|
|
|
|
result->second = ptr;
|
|
|
|
else
|
|
|
|
mSpellListCache.insert({id, ptr});
|
|
|
|
return {ptr, false};
|
|
|
|
}
|
|
|
|
return {ptr, true};
|
|
|
|
}
|
2022-07-03 11:47:46 +02:00
|
|
|
|
|
|
|
template <>
|
|
|
|
const ESM::Cell *ESMStore::insert<ESM::Cell>(const ESM::Cell &cell) {
|
2022-07-03 18:11:20 +02:00
|
|
|
return getWritable<ESM::Cell>().insert(cell);
|
2022-07-03 11:47:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
template <>
|
|
|
|
const ESM::NPC *ESMStore::insert<ESM::NPC>(const ESM::NPC &npc)
|
|
|
|
{
|
|
|
|
const std::string id = "$dynamic" + std::to_string(mDynamicCount++);
|
2022-07-19 17:58:21 +02:00
|
|
|
auto& npcs = getWritable<ESM::NPC>();
|
2022-07-03 11:47:46 +02:00
|
|
|
if (Misc::StringUtils::ciEqual(npc.mId, "player"))
|
|
|
|
{
|
2022-07-19 17:58:21 +02:00
|
|
|
return npcs.insert(npc);
|
2022-07-03 11:47:46 +02:00
|
|
|
}
|
2022-07-19 17:58:21 +02:00
|
|
|
else if (npcs.search(id) != nullptr)
|
2022-07-03 11:47:46 +02:00
|
|
|
{
|
|
|
|
const std::string msg = "Try to override existing record '" + id + "'";
|
|
|
|
throw std::runtime_error(msg);
|
|
|
|
}
|
|
|
|
ESM::NPC record = npc;
|
|
|
|
|
|
|
|
record.mId = id;
|
|
|
|
|
2022-07-19 17:58:21 +02:00
|
|
|
ESM::NPC *ptr = npcs.insert(record);
|
2022-07-20 19:28:06 +02:00
|
|
|
mStoreImp->mIds[ptr->mId] = ESM::REC_NPC_;
|
2022-07-03 11:47:46 +02:00
|
|
|
return ptr;
|
|
|
|
}
|
|
|
|
|
2022-08-01 19:03:07 +02:00
|
|
|
|
|
|
|
template<> const ESM::Book* ESMStore::insert<ESM::Book>(const ESM::Book &toInsert) { return ESMStoreImp::esm3StoreInsert(*this, toInsert);}
|
|
|
|
template<> const ESM::Armor* ESMStore::insert<ESM::Armor>(const ESM::Armor &toInsert) { return ESMStoreImp::esm3StoreInsert(*this, toInsert);}
|
|
|
|
template<> const ESM::Class* ESMStore::insert<ESM::Class>(const ESM::Class &toInsert) { return ESMStoreImp::esm3StoreInsert(*this, toInsert);}
|
|
|
|
template<> const ESM::Enchantment* ESMStore::insert<ESM::Enchantment>(const ESM::Enchantment &toInsert) { return ESMStoreImp::esm3StoreInsert(*this, toInsert);}
|
|
|
|
template<> const ESM::Potion* ESMStore::insert<ESM::Potion>(const ESM::Potion &toInsert) { return ESMStoreImp::esm3StoreInsert(*this, toInsert);}
|
|
|
|
template<> const ESM::Weapon* ESMStore::insert<ESM::Weapon>(const ESM::Weapon &toInsert) { return ESMStoreImp::esm3StoreInsert(*this, toInsert);}
|
|
|
|
template<> const ESM::Clothing* ESMStore::insert<ESM::Clothing>(const ESM::Clothing &toInsert) { return ESMStoreImp::esm3StoreInsert(*this, toInsert);}
|
|
|
|
template<> const ESM::Spell* ESMStore::insert<ESM::Spell>(const ESM::Spell &toInsert) { return ESMStoreImp::esm3StoreInsert(*this, toInsert);}
|
|
|
|
|
|
|
|
|
|
|
|
template<> const ESM::GameSetting* ESMStore::insertStatic<ESM::GameSetting>(const ESM::GameSetting &toInsert) { return ESMStoreImp::esm3insertStatic(*this, toInsert); }
|
|
|
|
template<> const ESM::Static* ESMStore::insertStatic<ESM::Static>(const ESM::Static &toInsert) { return ESMStoreImp::esm3insertStatic(*this, toInsert); }
|
|
|
|
template<> const ESM::Door* ESMStore::insertStatic<ESM::Door>(const ESM::Door &toInsert) { return ESMStoreImp::esm3insertStatic(*this, toInsert); }
|
|
|
|
template<> const ESM::Global* ESMStore::insertStatic<ESM::Global>(const ESM::Global &toInsert) { return ESMStoreImp::esm3insertStatic(*this, toInsert); }
|
|
|
|
template<> const ESM::NPC* ESMStore::insertStatic<ESM::NPC>(const ESM::NPC &toInsert) { return ESMStoreImp::esm3insertStatic(*this, toInsert); }
|
|
|
|
|
|
|
|
|
|
|
|
template<> const ESM::Container* ESMStore::overrideRecord<ESM::Container>(const ESM::Container &toInsert) { return ESMStoreImp::esm3overrideRecord(*this, toInsert); }
|
|
|
|
template<> const ESM::Creature* ESMStore::overrideRecord<ESM::Creature>(const ESM::Creature &toInsert) { return ESMStoreImp::esm3overrideRecord(*this, toInsert); }
|
|
|
|
template<> const ESM::CreatureLevList* ESMStore::overrideRecord<ESM::CreatureLevList>(const ESM::CreatureLevList &toInsert) { return ESMStoreImp::esm3overrideRecord(*this, toInsert); }
|
|
|
|
template<> const ESM::Door* ESMStore::overrideRecord<ESM::Door>(const ESM::Door &toInsert) { return ESMStoreImp::esm3overrideRecord(*this, toInsert); }
|
|
|
|
template<> const ESM::ItemLevList* ESMStore::overrideRecord<ESM::ItemLevList>(const ESM::ItemLevList &toInsert) { return ESMStoreImp::esm3overrideRecord(*this, toInsert); }
|
|
|
|
template<> const ESM::NPC* ESMStore::overrideRecord<ESM::NPC>(const ESM::NPC &toInsert) { return ESMStoreImp::esm3overrideRecord(*this, toInsert); }
|
2012-10-01 19:17:04 +04:00
|
|
|
} // end namespace
|