#include "store.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace { // TODO: Switch to C++23 to get a working version of std::unordered_map::erase template bool eraseFromMap(T& map, const Id& value) { auto it = map.find(value); if (it != map.end()) { map.erase(it); return true; } return false; } } namespace MWWorld { RecordId::RecordId(const ESM::RefId& id, bool isDeleted) : mId(id) , mIsDeleted(isDeleted) { } template IndexedStore::IndexedStore() { } template typename IndexedStore::iterator IndexedStore::begin() const { return mStatic.begin(); } template typename IndexedStore::iterator IndexedStore::end() const { return mStatic.end(); } template void IndexedStore::load(ESM::ESMReader& esm) { T record; bool isDeleted = false; record.load(esm, isDeleted); auto idx = record.mIndex; mStatic.insert_or_assign(idx, std::move(record)); } template int IndexedStore::getSize() const { return mStatic.size(); } template void IndexedStore::setUp() { } template const T* IndexedStore::search(int index) const { typename Static::const_iterator it = mStatic.find(index); if (it != mStatic.end()) return &(it->second); return nullptr; } template const T* IndexedStore::find(int index) const { const T* ptr = search(index); if (ptr == nullptr) { std::stringstream msg; msg << T::getRecordType() << " with index " << index << " not found"; throw std::runtime_error(msg.str()); } return ptr; } // Need to instantiate these before they're used template class IndexedStore; template class IndexedStore; template TypedDynamicStore::TypedDynamicStore() { } template TypedDynamicStore::TypedDynamicStore(const TypedDynamicStore& orig) : mStatic(orig.mStatic) { } template void TypedDynamicStore::clearDynamic() { // remove the dynamic part of mShared assert(mShared.size() >= mStatic.size()); mShared.erase(mShared.begin() + mStatic.size(), mShared.end()); mDynamic.clear(); } template const T* TypedDynamicStore::search(const Id& id) const { typename Dynamic::const_iterator dit = mDynamic.find(id); if (dit != mDynamic.end()) return &dit->second; typename Static::const_iterator it = mStatic.find(id); if (it != mStatic.end()) return &(it->second); return nullptr; } template const T* TypedDynamicStore::searchStatic(const Id& id) const { typename Static::const_iterator it = mStatic.find(id); if (it != mStatic.end()) return &(it->second); return nullptr; } template bool TypedDynamicStore::isDynamic(const Id& id) const { typename Dynamic::const_iterator dit = mDynamic.find(id); return (dit != mDynamic.end()); } template const T* TypedDynamicStore::searchRandom(const std::string_view prefix, Misc::Rng::Generator& prng) const { if constexpr (std::is_same_v) { std::vector results; std::copy_if(mShared.begin(), mShared.end(), std::back_inserter(results), [prefix](const T* item) { return item->mId.startsWith(prefix); }); if (!results.empty()) return results[Misc::Rng::rollDice(results.size(), prng)]; return nullptr; } else throw std::runtime_error("Store::searchRandom is supported only if Id is ESM::RefId"); } template const T* TypedDynamicStore::find(const Id& id) const { const T* ptr = search(id); if (ptr == nullptr) { std::stringstream msg; if constexpr (!ESM::isESM4Rec(T::sRecordId)) { msg << T::getRecordType(); } else { msg << "ESM::REC_" << getRecNameString(T::sRecordId).toStringView(); } msg << " '" << id << "' not found"; throw std::runtime_error(msg.str()); } return ptr; } template RecordId TypedDynamicStore::load(ESM::ESMReader& esm) { T record; bool isDeleted = false; if constexpr (!ESM::isESM4Rec(T::sRecordId)) { record.load(esm, isDeleted); } std::pair inserted = mStatic.insert_or_assign(record.mId, record); if (inserted.second) mShared.push_back(&inserted.first->second); if constexpr (std::is_same_v) return RecordId(record.mId, isDeleted); else return RecordId(); } template void TypedDynamicStore::setUp() { } template typename TypedDynamicStore::iterator TypedDynamicStore::begin() const { return mShared.begin(); } template typename TypedDynamicStore::iterator TypedDynamicStore::end() const { return mShared.end(); } template size_t TypedDynamicStore::getSize() const { return mShared.size(); } template int TypedDynamicStore::getDynamicSize() const { return mDynamic.size(); } template void TypedDynamicStore::listIdentifier(std::vector& list) const { list.reserve(list.size() + getSize()); typename std::vector::const_iterator it = mShared.begin(); for (; it != mShared.end(); ++it) { list.push_back((*it)->mId); } } template T* TypedDynamicStore::insert(const T& item, bool overrideOnly) { if (overrideOnly) { auto it = mStatic.find(item.mId); if (it == mStatic.end()) return nullptr; } std::pair result = mDynamic.insert_or_assign(item.mId, item); T* ptr = &result.first->second; if (result.second) mShared.push_back(ptr); return ptr; } template T* TypedDynamicStore::insertStatic(const T& item) { std::pair result = mStatic.insert_or_assign(item.mId, item); T* ptr = &result.first->second; if (result.second) mShared.push_back(ptr); return ptr; } template bool TypedDynamicStore::eraseStatic(const Id& id) { typename Static::iterator it = mStatic.find(id); if (it != mStatic.end()) { // delete from the static part of mShared typename std::vector::iterator sharedIter = mShared.begin(); typename std::vector::iterator end = sharedIter + mStatic.size(); while (sharedIter != mShared.end() && sharedIter != end) { if ((*sharedIter)->mId == id) { mShared.erase(sharedIter); break; } ++sharedIter; } mStatic.erase(it); } return true; } template bool TypedDynamicStore::erase(const Id& id) { if (!eraseFromMap(mDynamic, id)) return false; // have to reinit the whole shared part assert(mShared.size() >= mStatic.size()); mShared.erase(mShared.begin() + mStatic.size(), mShared.end()); for (auto it = mDynamic.begin(); it != mDynamic.end(); ++it) { mShared.push_back(&it->second); } return true; } template bool TypedDynamicStore::erase(const T& item) { return erase(item.mId); } template void TypedDynamicStore::write(ESM::ESMWriter& writer, Loading::Listener& progress) const { for (typename Dynamic::const_iterator iter(mDynamic.begin()); iter != mDynamic.end(); ++iter) { if constexpr (!ESM::isESM4Rec(T::sRecordId)) { writer.startRecord(T::sRecordId); iter->second.save(writer); writer.endRecord(T::sRecordId); } } } template RecordId TypedDynamicStore::read(ESM::ESMReader& reader, bool overrideOnly) { T record; bool isDeleted = false; if constexpr (!ESM::isESM4Rec(T::sRecordId)) { record.load(reader, isDeleted); } insert(record, overrideOnly); if constexpr (std::is_same_v) return RecordId(record.mId, isDeleted); else return RecordId(); } // LandTexture //========================================================================= Store::Store() {} const ESM::LandTexture* Store::search(size_t index, size_t plugin) const { assert(plugin < mStatic.size()); const LandTextureList& ltexl = mStatic[plugin]; if (index >= ltexl.size()) return nullptr; return <exl[index]; } const ESM::LandTexture* Store::find(size_t index, size_t plugin) const { const ESM::LandTexture* ptr = search(index, plugin); if (ptr == nullptr) { const std::string msg = "Land texture with index " + std::to_string(index) + " not found"; throw std::runtime_error(msg); } return ptr; } void Store::resize(std::size_t num) { mStatic.resize(num); } size_t Store::getSize() const { return mStatic.size(); } size_t Store::getSize(size_t plugin) const { assert(plugin < mStatic.size()); return mStatic[plugin].size(); } RecordId Store::load(ESM::ESMReader& esm) { ESM::LandTexture lt; bool isDeleted = false; lt.load(esm, isDeleted); // Replace texture for records with given ID and index from all plugins. for (unsigned int i = 0; i < mStatic.size(); i++) { ESM::LandTexture* tex = const_cast(search(lt.mIndex, i)); if (tex) { if (tex->mId == lt.mId) tex->mTexture = lt.mTexture; } } LandTextureList& ltexl = mStatic.back(); if (lt.mIndex + 1 > (int)ltexl.size()) ltexl.resize(lt.mIndex + 1); // Store it auto idx = lt.mIndex; ltexl[idx] = std::move(lt); return RecordId(ltexl[idx].mId, isDeleted); } Store::iterator Store::begin(size_t plugin) const { assert(plugin < mStatic.size()); return mStatic[plugin].begin(); } Store::iterator Store::end(size_t plugin) const { assert(plugin < mStatic.size()); return mStatic[plugin].end(); } // Land //========================================================================= Store::~Store() {} size_t Store::getSize() const { return mStatic.size(); } Store::iterator Store::begin() const { return iterator(mStatic.begin()); } Store::iterator Store::end() const { return iterator(mStatic.end()); } const ESM::Land* Store::search(int x, int y) const { std::pair comp(x, y); if (auto it = mStatic.find(comp); it != mStatic.end() && it->mX == x && it->mY == y) return &*it; return nullptr; } const ESM::Land* Store::find(int x, int y) const { const ESM::Land* ptr = search(x, y); if (ptr == nullptr) { const std::string msg = "Land at (" + std::to_string(x) + ", " + std::to_string(y) + ") not found"; throw std::runtime_error(msg); } return ptr; } RecordId Store::load(ESM::ESMReader& esm) { ESM::Land land; bool isDeleted = false; land.load(esm, isDeleted); // Same area defined in multiple plugins? -> last plugin wins auto it = mStatic.lower_bound(land); if (it != mStatic.end() && (std::tie(it->mX, it->mY) == std::tie(land.mX, land.mY))) { auto nh = mStatic.extract(it); nh.value() = std::move(land); mStatic.insert(std::move(nh)); } else mStatic.insert(it, std::move(land)); return RecordId(ESM::RefId(), isDeleted); } void Store::setUp() { // The land is static for given game session, there is no need to refresh it every load. if (mBuilt) return; mBuilt = true; } // Cell //========================================================================= const ESM::Cell* Store::search(const ESM::RefId& cellId) const { auto foundCellIt = mCells.find(cellId); if (foundCellIt != mCells.end()) return &foundCellIt->second; return nullptr; } const ESM::Cell* Store::search(const ESM::Cell& cell) const { return search(cell.mId); } // this method *must* be called right after esm3.loadCell() void Store::handleMovedCellRefs(ESM::ESMReader& esm, ESM::Cell* cell) { ESM::CellRef ref; ESM::MovedCellRef cMRef; bool deleted = false; bool moved = false; ESM::ESM_Context ctx = esm.getContext(); // Handling MovedCellRefs, there is no way to do it inside loadcell // TODO: verify above comment // // Get regular moved reference data. Adapted from CellStore::loadRefs. Maybe we can optimize the following // implementation when the oher implementation works as well. while (ESM::Cell::getNextRef(esm, ref, deleted, cMRef, moved, ESM::Cell::GetNextRefMode::LoadOnlyMoved)) { if (!moved) continue; ESM::Cell* cellAlt = const_cast(searchOrCreate(cMRef.mTarget[0], cMRef.mTarget[1])); // Add data required to make reference appear in the correct cell. // We should not need to test for duplicates, as this part of the code is pre-cell merge. cell->mMovedRefs.push_back(cMRef); // But there may be duplicates here! ESM::CellRefTracker::iterator iter = std::find_if( cellAlt->mLeasedRefs.begin(), cellAlt->mLeasedRefs.end(), ESM::CellRefTrackerPredicate(ref.mRefNum)); if (iter == cellAlt->mLeasedRefs.end()) cellAlt->mLeasedRefs.emplace_back(std::move(ref), deleted); else *iter = std::make_pair(std::move(ref), deleted); cMRef.mRefNum.mIndex = 0; } esm.restoreContext(ctx); } const ESM::Cell* Store::search(std::string_view name) const { DynamicInt::const_iterator it = mInt.find(name); if (it != mInt.end()) { return it->second; } DynamicInt::const_iterator dit = mDynamicInt.find(name); if (dit != mDynamicInt.end()) { return dit->second; } return nullptr; } const ESM::Cell* Store::search(int x, int y) const { std::pair key(x, y); DynamicExt::const_iterator it = mExt.find(key); if (it != mExt.end()) return it->second; DynamicExt::const_iterator dit = mDynamicExt.find(key); if (dit != mDynamicExt.end()) return dit->second; return nullptr; } const ESM::Cell* Store::searchStatic(int x, int y) const { DynamicExt::const_iterator it = mExt.find(std::make_pair(x, y)); if (it != mExt.end()) return (it->second); return nullptr; } const ESM::Cell* Store::searchOrCreate(int x, int y) { std::pair key(x, y); DynamicExt::const_iterator it = mExt.find(key); if (it != mExt.end()) return (it->second); DynamicExt::const_iterator dit = mDynamicExt.find(key); if (dit != mDynamicExt.end()) return dit->second; ESM::Cell newCell; newCell.mData.mX = x; newCell.mData.mY = y; newCell.mData.mFlags = ESM::Cell::HasWater; newCell.mAmbi.mAmbient = 0; newCell.mAmbi.mSunlight = 0; newCell.mAmbi.mFog = 0; newCell.mAmbi.mFogDensity = 0; newCell.updateId(); ESM::Cell* newCellInserted = &mCells.insert(std::make_pair(newCell.mId, newCell)).first->second; return mExt.insert(std::make_pair(key, newCellInserted)).first->second; } const ESM::Cell* Store::find(const ESM::RefId& id) const { const ESM::Cell* ptr = search(id); if (ptr == nullptr) { const std::string msg = "Cell " + id.toDebugString() + " not found"; throw std::runtime_error(msg); } return ptr; } const ESM::Cell* Store::find(std::string_view id) const { const ESM::Cell* ptr = search(id); if (ptr == nullptr) { const std::string msg = "Cell '" + std::string(id) + "' not found"; throw std::runtime_error(msg); } return ptr; } const ESM::Cell* Store::find(int x, int y) const { const ESM::Cell* ptr = search(x, y); if (ptr == nullptr) { const std::string msg = "Exterior at (" + std::to_string(x) + ", " + std::to_string(y) + ") not found"; throw std::runtime_error(msg); } return ptr; } void Store::clearDynamic() { setUp(); } void Store::setUp() { mSharedInt.clear(); mSharedInt.reserve(mInt.size()); for (auto& [_, cell] : mInt) mSharedInt.push_back(cell); mSharedExt.clear(); mSharedExt.reserve(mExt.size()); for (auto& [_, cell] : mExt) mSharedExt.push_back(cell); } RecordId Store::load(ESM::ESMReader& esm) { // Don't automatically assume that a new cell must be spawned. Multiple plugins write to the same cell, // and we merge all this data into one Cell object. However, we can't simply search for the cell id, // as many exterior cells do not have a name. Instead, we need to search by (x,y) coordinates - and they // are not available until both cells have been loaded at least partially! // All cells have a name record, even nameless exterior cells. ESM::Cell* emplacedCell = nullptr; bool isDeleted = false; bool newCell = false; { ESM::Cell cellToLoad; cellToLoad.loadNameAndData(esm, isDeleted); auto [it, inserted] = mCells.insert(std::make_pair(cellToLoad.mId, cellToLoad)); emplacedCell = &it->second; if (!inserted) { emplacedCell->mData = cellToLoad.mData; emplacedCell->mName = cellToLoad.mName; } newCell = inserted; } ESM::Cell& cell = *emplacedCell; // Load the (x,y) coordinates of the cell, if it is an exterior cell, // so we can find the cell we need to merge with if (cell.mData.mFlags & ESM::Cell::Interior) { cell.loadCell(esm, true); if (newCell) mInt[cell.mName] = &cell; } else { cell.loadCell(esm, false); // handle moved ref (MVRF) subrecords ESM::MovedCellRefTracker newMovedRefs; std::swap(newMovedRefs, cell.mMovedRefs); handleMovedCellRefs(esm, &cell); std::swap(newMovedRefs, cell.mMovedRefs); // push the new references on the list of references to manage cell.postLoad(esm); if (newCell) mExt[std::make_pair(cell.mData.mX, cell.mData.mY)] = &cell; else { // merge lists of leased references, use newer data in case of conflict for (const auto& movedRef : newMovedRefs) { // remove reference from current leased ref tracker and add it to new cell auto itOld = std::find(cell.mMovedRefs.begin(), cell.mMovedRefs.end(), movedRef.mRefNum); if (itOld != cell.mMovedRefs.end()) { if (movedRef.mTarget[0] != itOld->mTarget[0] || movedRef.mTarget[1] != itOld->mTarget[1]) { ESM::Cell* wipecell = const_cast(search(itOld->mTarget[0], itOld->mTarget[1])); auto itLease = std::find_if(wipecell->mLeasedRefs.begin(), wipecell->mLeasedRefs.end(), ESM::CellRefTrackerPredicate(movedRef.mRefNum)); if (itLease != wipecell->mLeasedRefs.end()) wipecell->mLeasedRefs.erase(itLease); else Log(Debug::Error) << "Error: can't find " << movedRef.mRefNum.mIndex << " " << movedRef.mRefNum.mContentFile << " in leasedRefs"; } *itOld = movedRef; } else cell.mMovedRefs.push_back(movedRef); } // We don't need to merge mLeasedRefs of cell / oldcell. This list is filled when another cell moves a // reference to this cell, so the list for the new cell should be empty. The list for oldcell, // however, could have leased refs in it and so should be kept. } } return RecordId(cell.mId, isDeleted); } Store::iterator Store::intBegin() const { return iterator(mSharedInt.begin()); } Store::iterator Store::intEnd() const { return iterator(mSharedInt.end()); } Store::iterator Store::extBegin() const { return iterator(mSharedExt.begin()); } Store::iterator Store::extEnd() const { return iterator(mSharedExt.end()); } const ESM::Cell* Store::searchExtByName(std::string_view name) const { const ESM::Cell* cell = nullptr; for (const ESM::Cell* sharedCell : mSharedExt) { if (Misc::StringUtils::ciEqual(sharedCell->mName, name)) { if (cell == nullptr || (sharedCell->mData.mX > cell->mData.mX) || (sharedCell->mData.mX == cell->mData.mX && sharedCell->mData.mY > cell->mData.mY)) { cell = sharedCell; } } } return cell; } const ESM::Cell* Store::searchExtByRegion(const ESM::RefId& id) const { const ESM::Cell* cell = nullptr; for (const ESM::Cell* sharedCell : mSharedExt) { if (sharedCell->mRegion == id) { if (cell == nullptr || (sharedCell->mData.mX > cell->mData.mX) || (sharedCell->mData.mX == cell->mData.mX && sharedCell->mData.mY > cell->mData.mY)) { cell = sharedCell; } } } return cell; } size_t Store::getSize() const { return mSharedInt.size() + mSharedExt.size(); } size_t Store::getExtSize() const { return mSharedExt.size(); } size_t Store::getIntSize() const { return mSharedInt.size(); } void Store::listIdentifier(std::vector& list) const { list.reserve(list.size() + mSharedInt.size()); for (const ESM::Cell* sharedCell : mSharedInt) { list.push_back(ESM::RefId::stringRefId(sharedCell->mName)); } } ESM::Cell* Store::insert(const ESM::Cell& cell) { if (search(cell) != nullptr) { const std::string cellType = (cell.isExterior()) ? "exterior" : "interior"; throw std::runtime_error("Failed to create " + cellType + " cell"); } ESM::Cell* insertedCell = &mCells.emplace(cell.mId, cell).first->second; if (cell.isExterior()) { std::pair key(cell.getGridX(), cell.getGridY()); // duplicate insertions are avoided by search(ESM::Cell &) DynamicExt::iterator result = mDynamicExt.emplace(key, insertedCell).first; mSharedExt.push_back(result->second); return result->second; } else { // duplicate insertions are avoided by search(ESM::Cell &) DynamicInt::iterator result = mDynamicInt.emplace(cell.mName, insertedCell).first; mSharedInt.push_back(result->second); return result->second; } } // Pathgrid //========================================================================= Store::Store() : mCells(nullptr) { } void Store::setCells(Store& cells) { mCells = &cells; } RecordId Store::load(ESM::ESMReader& esm) { ESM::Pathgrid pathgrid; bool isDeleted = false; pathgrid.load(esm, isDeleted); // Unfortunately the Pathgrid record model does not specify whether the pathgrid belongs to an interior or // exterior cell. For interior cells, mCell is the cell name, but for exterior cells it is either the cell name // or if that doesn't exist, the cell's region name. mX and mY will be (0,0) for interior cells, but there is // also an exterior cell with the coordinates of (0,0), so that doesn't help. Check whether mCell is an interior // cell. This isn't perfect, will break if a Region with the same name as an interior cell is created. A proper // fix should be made for future versions of the file format. bool interior = pathgrid.mData.mX == 0 && pathgrid.mData.mY == 0 && mCells->search(pathgrid.mCell.getRefIdString()) != nullptr; ESM::RefId cell = interior ? pathgrid.mCell : ESM::RefId::esm3ExteriorCell(pathgrid.mData.mX, pathgrid.mData.mY); // deal with mods that have empty pathgrid records (Issue #6209) // we assume that these records are empty on purpose (i.e. to remove old pathgrid on an updated cell) if (isDeleted || pathgrid.mPoints.empty() || pathgrid.mEdges.empty()) { mStatic.erase(cell); return RecordId(ESM::RefId(), isDeleted); } // Try to overwrite existing record auto ret = mStatic.emplace(cell, pathgrid); if (!ret.second) ret.first->second = pathgrid; return RecordId(ESM::RefId(), isDeleted); } size_t Store::getSize() const { return mStatic.size(); } void Store::setUp() {} const ESM::Pathgrid* Store::search(const ESM::RefId& name) const { auto it = mStatic.find(name); if (it != mStatic.end()) return &(it->second); return nullptr; } const ESM::Pathgrid* Store::find(const ESM::RefId& name) const { const ESM::Pathgrid* pathgrid = search(name); if (pathgrid == nullptr) throw std::runtime_error("Pathgrid in cell " + name.toDebugString() + " is not found"); return pathgrid; } const ESM::Pathgrid* Store::search(const ESM::Cell& cell) const { return search(cell.mId); } const ESM::Pathgrid* Store::search(const MWWorld::Cell& cellVariant) const { return ESM::visit(ESM::VisitOverload{ [&](const ESM::Cell& cell) { return search(cell); }, [&](const ESM4::Cell& cell) -> const ESM::Pathgrid* { return nullptr; }, }, cellVariant); } const ESM::Pathgrid* Store::find(const ESM::Cell& cell) const { return find(cell.mId); } // Skill //========================================================================= Store::Store() {} // Game Settings //========================================================================= const ESM::GameSetting* Store::search(const ESM::RefId& id) const { return TypedDynamicStore::search(id); } const ESM::GameSetting* Store::find(std::string_view id) const { return TypedDynamicStore::find(ESM::RefId::stringRefId(id)); } const ESM::GameSetting* Store::search(std::string_view id) const { return TypedDynamicStore::search(ESM::RefId::stringRefId(id)); } // Magic effect //========================================================================= Store::Store() {} // Attribute //========================================================================= Store::Store() { mStatic.reserve(ESM::Attribute::Length); } const ESM::Attribute* Store::search(size_t index) const { if (index >= mStatic.size()) { return nullptr; } return &mStatic[index]; } const ESM::Attribute* Store::find(size_t index) const { const ESM::Attribute* ptr = search(index); if (ptr == nullptr) { const std::string msg = "Attribute with index " + std::to_string(index) + " not found"; throw std::runtime_error(msg); } return ptr; } void Store::setUp() { for (int i = 0; i < ESM::Attribute::Length; ++i) { ESM::Attribute newAttribute; newAttribute.mId = ESM::Attribute::sAttributeIds[i]; newAttribute.mName = ESM::Attribute::sGmstAttributeIds[i]; newAttribute.mDescription = ESM::Attribute::sGmstAttributeDescIds[i]; mStatic.push_back(newAttribute); } } size_t Store::getSize() const { return mStatic.size(); } Store::iterator Store::begin() const { return mStatic.begin(); } Store::iterator Store::end() const { return mStatic.end(); } // Dialogue //========================================================================= Store::Store() : mKeywordSearchModFlag(true) { } void Store::setUp() { // DialInfos marked as deleted are kept during the loading phase, so that the linked list // structure is kept intact for inserting further INFOs. Delete them now that loading is done. for (auto& [_, dial] : mStatic) dial.setUp(); mShared.clear(); mShared.reserve(mStatic.size()); for (auto& [_, dial] : mStatic) mShared.push_back(&dial); // TODO: verify and document this inconsistent behaviour // TODO: if we require this behaviour, maybe we should move it to the place that requires it std::sort(mShared.begin(), mShared.end(), [](const ESM::Dialogue* l, const ESM::Dialogue* r) -> bool { return l->mId < r->mId; }); mKeywordSearchModFlag = true; } const ESM::Dialogue* Store::search(const ESM::RefId& id) const { typename Static::const_iterator it = mStatic.find(id); if (it != mStatic.end()) return &(it->second); return nullptr; } const ESM::Dialogue* Store::find(const ESM::RefId& id) const { const ESM::Dialogue* ptr = search(id); if (ptr == nullptr) { std::stringstream msg; msg << ESM::Dialogue::getRecordType() << " '" << id << "' not found"; throw std::runtime_error(msg.str()); } return ptr; } typename Store::iterator Store::begin() const { return mShared.begin(); } typename Store::iterator Store::end() const { return mShared.end(); } size_t Store::getSize() const { return mShared.size(); } inline RecordId Store::load(ESM::ESMReader& esm) { // The original letter case of a dialogue ID is saved, because it's printed ESM::Dialogue dialogue; bool isDeleted = false; dialogue.loadId(esm); Static::iterator found = mStatic.find(dialogue.mId); if (found == mStatic.end()) { dialogue.loadData(esm, isDeleted); mStatic.emplace(dialogue.mId, dialogue); } else { found->second.loadData(esm, isDeleted); dialogue.mId = found->second.mId; } mKeywordSearchModFlag = true; return RecordId(dialogue.mId, isDeleted); } bool Store::eraseStatic(const ESM::RefId& id) { if (eraseFromMap(mStatic, id)) mKeywordSearchModFlag = true; return true; } void Store::listIdentifier(std::vector& list) const { list.reserve(list.size() + getSize()); for (const auto& dialogue : mShared) list.push_back(dialogue->mId); } const MWDialogue::KeywordSearch& Store::getDialogIdKeywordSearch() const { if (mKeywordSearchModFlag) { mKeywordSearch.clear(); std::vector keywordList; keywordList.reserve(getSize()); for (const auto& it : *this) keywordList.push_back(Misc::StringUtils::lowerCase(it.mStringId)); sort(keywordList.begin(), keywordList.end()); for (const auto& it : keywordList) mKeywordSearch.seed(it, 0 /*unused*/); mKeywordSearchModFlag = false; } return mKeywordSearch; } // ESM4 Cell //========================================================================= const ESM4::Cell* Store::searchCellName(std::string_view cellName) const { const auto foundCell = mCellNameIndex.find(cellName); if (foundCell == mCellNameIndex.end()) return nullptr; return foundCell->second; } const ESM4::Cell* Store::searchExterior(ESM::ExteriorCellLocation cellIndex) const { const auto foundCell = mExteriors.find(cellIndex); if (foundCell == mExteriors.end()) return nullptr; return foundCell->second; } ESM4::Cell* Store::insert(const ESM4::Cell& item, bool overrideOnly) { auto cellPtr = TypedDynamicStore::insert(item, overrideOnly); insertCell(cellPtr); return cellPtr; } ESM4::Cell* Store::insertStatic(const ESM4::Cell& item) { auto cellPtr = TypedDynamicStore::insertStatic(item); insertCell(cellPtr); return cellPtr; } void Store::insertCell(ESM4::Cell* cellPtr) { if (!cellPtr->mEditorId.empty()) mCellNameIndex[cellPtr->mEditorId] = cellPtr; if (cellPtr->isExterior()) { ESM::ExteriorCellLocation cellindex = { cellPtr->mX, cellPtr->mY, cellPtr->mParent }; if (cellPtr->mCellFlags & ESM4::Rec_Persistent) mPersistentExteriors[cellindex] = cellPtr; else mExteriors[cellindex] = cellPtr; } } void Store::clearDynamic() { for (auto& cellToDeleteIt : mDynamic) { ESM4::Cell& cellToDelete = cellToDeleteIt.second; if (cellToDelete.isExterior()) { mExteriors.erase({ cellToDelete.mX, cellToDelete.mY, cellToDelete.mParent }); } if (!cellToDelete.mEditorId.empty()) mCellNameIndex.erase(cellToDelete.mEditorId); } MWWorld::TypedDynamicStore::clearDynamic(); } // ESM4 Reference //========================================================================= void Store::preprocessReferences(const Store& cells) { for (auto& [_, ref] : mStatic) { const ESM4::Cell* cell = cells.find(ref.mParent); if (cell->isExterior() && (cell->mFlags & ESM4::Rec_Persistent)) { const ESM4::Cell* actualCell = cells.searchExterior(positionToCellIndex(ref.mPos.pos[0], ref.mPos.pos[1], cell->mParent)); if (actualCell) ref.mParent = actualCell->mId; } mPerCellReferences[ref.mParent].push_back(&ref); } } std::span Store::getByCell(ESM::RefId cellId) const { auto it = mPerCellReferences.find(cellId); if (it == mPerCellReferences.end()) return {}; return it->second; } } template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; // template class MWWorld::Store; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; // template class MWWorld::Store; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; // template class MWWorld::Store; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; // template class MWWorld::Store; // template class MWWorld::Store; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; // template class MWWorld::Store; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; // template class MWWorld::Store; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; // template class MWWorld::Store; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore;