#ifndef OPENMW_MWWORLD_STORE_H #define OPENMW_MWWORLD_STORE_H #include <map> #include <memory> #include <set> #include <span> #include <string> #include <unordered_map> #include <vector> #include <components/esm/attr.hpp> #include <components/esm/refid.hpp> #include <components/esm/util.hpp> #include <components/esm3/loadcell.hpp> #include <components/esm3/loaddial.hpp> #include <components/esm3/loadglob.hpp> #include <components/esm3/loadgmst.hpp> #include <components/esm3/loadland.hpp> #include <components/esm3/loadpgrd.hpp> #include <components/esm3/loadskil.hpp> #include <components/esm4/loadachr.hpp> #include <components/esm4/loadcell.hpp> #include <components/esm4/loadland.hpp> #include <components/esm4/loadrefr.hpp> #include <components/misc/rng.hpp> #include <components/misc/strings/algorithm.hpp> #include "../mwdialogue/keywordsearch.hpp" namespace ESM { struct LandTexture; struct MagicEffect; struct WeaponType; class ESMReader; class ESMWriter; } namespace Loading { class Listener; } namespace MWWorld { class Cell; struct RecordId { ESM::RefId mId; bool mIsDeleted; RecordId(const ESM::RefId& id = {}, bool isDeleted = false); }; class StoreBase { }; // Empty interface to be parent of all store types template <class Id> class DynamicStoreBase : public StoreBase { public: virtual ~DynamicStoreBase() {} virtual void setUp() {} /// List identifiers of records contained in this Store (case-smashed). No-op for Stores that don't use string /// IDs. virtual void listIdentifier(std::vector<Id>& list) const {} virtual size_t getSize() const = 0; virtual int getDynamicSize() const { return 0; } virtual RecordId load(ESM::ESMReader& esm) = 0; virtual bool eraseStatic(const Id& id) { return false; } virtual void clearDynamic() {} virtual void write(ESM::ESMWriter& writer, Loading::Listener& progress) const {} virtual RecordId read(ESM::ESMReader& reader, bool overrideOnly = false) { return RecordId(); } ///< Read into dynamic storage }; using DynamicStore = DynamicStoreBase<ESM::RefId>; template <class T> class IndexedStore : public StoreBase { protected: typedef typename std::map<int, T> Static; Static mStatic; public: typedef typename std::map<int, T>::const_iterator iterator; IndexedStore(); iterator begin() const; iterator end() const; iterator findIter(int index) const { return mStatic.find(index); } void load(ESM::ESMReader& esm); int getSize() const; void setUp(); const T* search(int index) const; // calls `search` and throws an exception if not found const T* find(int index) const; }; template <class T> class SharedIterator { public: using Iter = typename std::vector<T*>::const_iterator; using iterator_category = std::random_access_iterator_tag; using value_type = T; using difference_type = std::ptrdiff_t; using pointer = T*; using reference = T&; SharedIterator() = default; SharedIterator(const SharedIterator& other) = default; SharedIterator(const Iter& iter) : mIter(iter) { } SharedIterator& operator=(const SharedIterator&) = default; SharedIterator& operator++() { ++mIter; return *this; } SharedIterator operator++(int) { SharedIterator iter = *this; ++mIter; return iter; } SharedIterator& operator+=(difference_type advance) { mIter += advance; return *this; } SharedIterator& operator--() { --mIter; return *this; } SharedIterator operator--(int) { SharedIterator iter = *this; --mIter; return iter; } bool operator==(const SharedIterator& x) const { return mIter == x.mIter; } bool operator!=(const SharedIterator& x) const { return !(*this == x); } const T& operator*() const { return **mIter; } const T* operator->() const { return &(**mIter); } private: Iter mIter; friend inline difference_type operator-(const SharedIterator& lhs, const SharedIterator& rhs) { return lhs.mIter - rhs.mIter; } }; class ESMStore; template <class T, class Id = ESM::RefId> class TypedDynamicStore : public DynamicStoreBase<Id> { protected: typedef std::unordered_map<Id, T> Static; Static mStatic; /// @par mShared usually preserves the record order as it came from the content files (this /// is relevant for the spell autocalc code and selection order /// for heads/hairs in the character creation) std::vector<T*> mShared; typedef std::unordered_map<Id, T> Dynamic; Dynamic mDynamic; friend class ESMStore; public: TypedDynamicStore(); TypedDynamicStore(const TypedDynamicStore<T, Id>& orig); typedef SharedIterator<T> iterator; // setUp needs to be called again after void clearDynamic() override; void setUp() override; const T* search(const Id& id) const; const T* searchStatic(const Id& id) const; /** * Does the record with this ID come from the dynamic store? */ bool isDynamic(const Id& id) const; /** Returns a random record that starts with the named ID, or nullptr if not found. */ const T* searchRandom(const std::string_view prefix, Misc::Rng::Generator& prng) const; // calls `search` and throws an exception if not found const T* find(const Id& id) const; iterator begin() const; iterator end() const; const T* at(size_t index) const { return mShared.at(index); } size_t getSize() const override; int getDynamicSize() const override; /// @note The record identifiers are listed in the order that the records were defined by the content files. void listIdentifier(std::vector<Id>& list) const override; T* insert(const T& item, bool overrideOnly = false); T* insertStatic(const T& item); bool eraseStatic(const Id& id) override; bool erase(const Id& id); bool erase(const T& item); RecordId load(ESM::ESMReader& esm) override; void write(ESM::ESMWriter& writer, Loading::Listener& progress) const override; RecordId read(ESM::ESMReader& reader, bool overrideOnly = false) override; }; template <class T> class Store : public TypedDynamicStore<T> { }; template <> class Store<ESM::LandTexture> : public DynamicStore { using PluginIndex = std::pair<int, std::uint32_t>; // This is essentially a FormId std::unordered_map<ESM::RefId, std::string> mStatic; std::map<PluginIndex, ESM::RefId> mMappings; public: Store(); // Must be threadsafe! Called from terrain background loading threads. // Not a big deal here, since ESM::LandTexture can never be modified or inserted/erased const std::string* search(std::uint32_t index, int plugin) const; size_t getSize() const override; bool eraseStatic(const ESM::RefId& id) override; RecordId load(ESM::ESMReader& esm) override; }; template <> class Store<ESM::GameSetting> : public TypedDynamicStore<ESM::GameSetting> { public: const ESM::GameSetting* search(const ESM::RefId& id) const; const ESM::GameSetting* find(const std::string_view id) const; const ESM::GameSetting* search(const std::string_view id) const; void setUp() override; }; template <> class Store<ESM4::Cell> : public TypedDynamicStore<ESM4::Cell> { std::unordered_map<std::string, ESM4::Cell*, Misc::StringUtils::CiHash, Misc::StringUtils::CiEqual> mCellNameIndex; std::unordered_map<ESM::ExteriorCellLocation, ESM4::Cell*> mExteriors; public: const ESM4::Cell* searchCellName(std::string_view) const; const ESM4::Cell* searchExterior(ESM::ExteriorCellLocation cellIndex) const; ESM4::Cell* insert(const ESM4::Cell& item, bool overrideOnly = false); ESM4::Cell* insertStatic(const ESM4::Cell& item); void insertCell(ESM4::Cell* cell); void clearDynamic() override; }; template <> class Store<ESM4::Land> : public TypedDynamicStore<ESM4::Land> { std::unordered_map<ESM::ExteriorCellLocation, const ESM4::Land*> mLands; public: Store(); void updateLandPositions(const Store<ESM4::Cell>& cells); const ESM4::Land* search(ESM::ExteriorCellLocation cellLocation) const; const std::unordered_map<ESM::ExteriorCellLocation, const ESM4::Land*>& getLands() const { return mLands; } }; template <> class Store<ESM::Land> : public DynamicStore { struct SpatialComparator { using is_transparent = void; bool operator()(const ESM::Land& x, const ESM::Land& y) const { return std::tie(x.mX, x.mY) < std::tie(y.mX, y.mY); } bool operator()(const ESM::Land& x, const std::pair<int, int>& y) const { return std::tie(x.mX, x.mY) < std::tie(y.first, y.second); } bool operator()(const std::pair<int, int>& x, const ESM::Land& y) const { return std::tie(x.first, x.second) < std::tie(y.mX, y.mY); } }; using Statics = std::set<ESM::Land, SpatialComparator>; Statics mStatic; public: typedef typename Statics::iterator iterator; virtual ~Store(); size_t getSize() const override; iterator begin() const; iterator end() const; // Must be threadsafe! Called from terrain background loading threads. // Not a big deal here, since ESM::Land can never be modified or inserted/erased const ESM::Land* search(int x, int y) const; const ESM::Land* find(int x, int y) const; RecordId load(ESM::ESMReader& esm) override; void setUp() override; private: bool mBuilt = false; }; template <> class Store<ESM::Cell> : public DynamicStore { struct DynamicExtCmp { bool operator()(const std::pair<int, int>& left, const std::pair<int, int>& right) const { if (left.first == right.first && left.second == right.second) return false; if (left.first == right.first) return left.second > right.second; // Exterior cells are listed in descending, row-major order, // this is a workaround for an ambiguous chargen_plank reference in the vanilla game. // there is one at -22,16 and one at -2,-9, the latter should be used. return left.first > right.first; } }; typedef std::unordered_map<std::string, ESM::Cell*, Misc::StringUtils::CiHash, Misc::StringUtils::CiEqual> DynamicInt; typedef std::map<std::pair<int, int>, ESM::Cell*, DynamicExtCmp> DynamicExt; std::unordered_map<ESM::RefId, ESM::Cell> mCells; DynamicInt mInt; DynamicExt mExt; std::vector<ESM::Cell*> mSharedInt; std::vector<ESM::Cell*> mSharedExt; DynamicInt mDynamicInt; DynamicExt mDynamicExt; const ESM::Cell* search(const ESM::Cell& cell) const; void handleMovedCellRefs(ESM::ESMReader& esm, ESM::Cell* cell); public: typedef SharedIterator<ESM::Cell> iterator; const ESM::Cell* search(const ESM::RefId& id) const; const ESM::Cell* search(std::string_view id) const; const ESM::Cell* search(int x, int y) const; const ESM::Cell* searchStatic(int x, int y) const; const ESM::Cell* searchOrCreate(int x, int y); const ESM::Cell* find(const ESM::RefId& id) const; const ESM::Cell* find(std::string_view id) const; const ESM::Cell* find(int x, int y) const; void clearDynamic() override; void setUp() override; RecordId load(ESM::ESMReader& esm) override; iterator intBegin() const; iterator intEnd() const; iterator extBegin() const; iterator extEnd() const; // Return the northernmost cell in the easternmost column. const ESM::Cell* searchExtByName(std::string_view id) const; // Return the northernmost cell in the easternmost column. const ESM::Cell* searchExtByRegion(const ESM::RefId& id) const; size_t getSize() const override; size_t getExtSize() const; size_t getIntSize() const; const ESM::Cell* at(size_t index) const { if (index < mSharedInt.size()) return mSharedInt.at(index); else return mSharedExt.at(index - mSharedInt.size()); } void listIdentifier(std::vector<ESM::RefId>& list) const override; ESM::Cell* insert(const ESM::Cell& cell); }; template <> class Store<ESM::Pathgrid> : public DynamicStore { private: std::unordered_map<ESM::RefId, ESM::Pathgrid> mStatic; Store<ESM::Cell>* mCells; public: Store(); void setCells(Store<ESM::Cell>& cells); RecordId load(ESM::ESMReader& esm) override; size_t getSize() const override; void setUp() override; const ESM::Pathgrid* search(const ESM::RefId& name) const; const ESM::Pathgrid* find(const ESM::RefId& name) const; const ESM::Pathgrid* search(const ESM::Cell& cell) const; const ESM::Pathgrid* search(const MWWorld::Cell& cell) const; const ESM::Pathgrid* find(const ESM::Cell& cell) const; }; template <> class Store<ESM::Skill> : public TypedDynamicStore<ESM::Skill> { using TypedDynamicStore<ESM::Skill>::setUp; public: Store() = default; void setUp(const MWWorld::Store<ESM::GameSetting>& settings); }; template <> class Store<ESM::MagicEffect> : public IndexedStore<ESM::MagicEffect> { public: Store(); }; template <> class Store<ESM::Attribute> : public TypedDynamicStore<ESM::Attribute> { using TypedDynamicStore<ESM::Attribute>::setUp; public: Store() = default; void setUp(const MWWorld::Store<ESM::GameSetting>& settings); }; template <> class Store<ESM::WeaponType> : public DynamicStore { std::map<int, ESM::WeaponType> mStatic; public: typedef std::map<int, ESM::WeaponType>::const_iterator iterator; Store(); const ESM::WeaponType* search(const int id) const; // calls `search` and throws an exception if not found const ESM::WeaponType* find(const int id) const; RecordId load(ESM::ESMReader& esm) override { return RecordId({}, false); } ESM::WeaponType* insert(const ESM::WeaponType& weaponType); void setUp() override; size_t getSize() const override; iterator begin() const; iterator end() const; }; template <> class Store<ESM::Dialogue> : public DynamicStore { typedef std::unordered_map<ESM::RefId, ESM::Dialogue> Static; Static mStatic; /// @par mShared usually preserves the record order as it came from the content files (this /// is relevant for the spell autocalc code and selection order /// for heads/hairs in the character creation) /// @warning ESM::Dialogue Store currently implements a sorted order for unknown reasons. std::vector<ESM::Dialogue*> mShared; mutable bool mKeywordSearchModFlag; mutable MWDialogue::KeywordSearch<int /*unused*/> mKeywordSearch; public: Store(); typedef SharedIterator<ESM::Dialogue> iterator; void setUp() override; const ESM::Dialogue* search(const ESM::RefId& id) const; const ESM::Dialogue* find(const ESM::RefId& id) const; iterator begin() const; iterator end() const; size_t getSize() const override; bool eraseStatic(const ESM::RefId& id) override; RecordId load(ESM::ESMReader& esm) override; void listIdentifier(std::vector<ESM::RefId>& list) const override; const MWDialogue::KeywordSearch<int>& getDialogIdKeywordSearch() const; }; template <typename T> class ESM4RefsStore : public TypedDynamicStore<T, ESM::FormId> { public: void preprocessReferences(const Store<ESM4::Cell>& cells) { for (auto& [_, ref] : this->mStatic) { const ESM4::Cell* cell = cells.find(ref.mParent); if (cell->isExterior() && (cell->mFlags & ESM4::Rec_Persistent)) { const ESM4::Cell* actualCell = cells.searchExterior( positionToExteriorCellLocation(ref.mPos.pos[0], ref.mPos.pos[1], cell->mParent)); if (actualCell) ref.mParent = actualCell->mId; } mPerCellReferences[ref.mParent].push_back(&ref); } } std::span<const T* const> getByCell(ESM::RefId cellId) const { auto it = mPerCellReferences.find(cellId); if (it == mPerCellReferences.end()) return {}; return it->second; } private: std::unordered_map<ESM::RefId, std::vector<T*>> mPerCellReferences; }; template <> class Store<ESM4::Reference> : public ESM4RefsStore<ESM4::Reference> { }; template <> class Store<ESM4::ActorCharacter> : public ESM4RefsStore<ESM4::ActorCharacter> { }; template <> class Store<ESM4::ActorCreature> : public ESM4RefsStore<ESM4::ActorCreature> { }; } // end namespace #endif