#ifndef OPENMW_MWWORLD_STORE_H #define OPENMW_MWWORLD_STORE_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../mwdialogue/keywordsearch.hpp" namespace ESM { struct Attribute; struct LandTexture; struct MagicEffect; struct WeaponType; class ESMReader; class ESMWriter; } namespace ESM4 { struct Land; } 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 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& 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; template class IndexedStore : public StoreBase { protected: typedef typename std::map Static; Static mStatic; public: typedef typename std::map::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 SharedIterator { public: using Iter = typename std::vector::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 TypedDynamicStore : public DynamicStoreBase { protected: typedef std::unordered_map 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 mShared; typedef std::unordered_map Dynamic; Dynamic mDynamic; friend class ESMStore; public: TypedDynamicStore(); TypedDynamicStore(const TypedDynamicStore& orig); typedef SharedIterator 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& 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 Store : public TypedDynamicStore { }; template <> class Store : public DynamicStore { // For multiple ESM/ESP files we need one list per file. typedef std::vector LandTextureList; std::vector mStatic; public: Store(); typedef std::vector::const_iterator iterator; // 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 ESM::LandTexture* search(size_t index, size_t plugin) const; const ESM::LandTexture* find(size_t index, size_t plugin) const; void resize(std::size_t num); size_t getSize() const override; size_t getSize(size_t plugin) const; RecordId load(ESM::ESMReader& esm) override; iterator begin(size_t plugin) const; iterator end(size_t plugin) const; }; template <> class Store : public TypedDynamicStore { 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 : public TypedDynamicStore { std::unordered_map mCellNameIndex; std::unordered_map 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 : public TypedDynamicStore { std::unordered_map mLands; public: Store(); void updateLandPositions(const Store& cells); const ESM4::Land* search(ESM::ExteriorCellLocation cellLocation) const; const std::unordered_map& getLands() const { return mLands; } }; template <> class Store : 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& y) const { return std::tie(x.mX, x.mY) < std::tie(y.first, y.second); } bool operator()(const std::pair& x, const ESM::Land& y) const { return std::tie(x.first, x.second) < std::tie(y.mX, y.mY); } }; using Statics = std::set; 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 : public DynamicStore { struct DynamicExtCmp { bool operator()(const std::pair& left, const std::pair& 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 DynamicInt; typedef std::map, ESM::Cell*, DynamicExtCmp> DynamicExt; std::unordered_map mCells; DynamicInt mInt; DynamicExt mExt; std::vector mSharedInt; std::vector 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 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& list) const override; ESM::Cell* insert(const ESM::Cell& cell); }; template <> class Store : public DynamicStore { private: std::unordered_map mStatic; Store* mCells; public: Store(); void setCells(Store& 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 : public TypedDynamicStore { using TypedDynamicStore::setUp; public: Store() = default; void setUp(const MWWorld::Store& settings); }; template <> class Store : public IndexedStore { public: Store(); }; template <> class Store : public IndexedStore { std::vector mStatic; public: typedef std::vector::const_iterator iterator; Store(); const ESM::Attribute* search(size_t index) const; // calls `search` and throws an exception if not found const ESM::Attribute* find(size_t index) const; void setUp(const MWWorld::Store& settings); size_t getSize() const; iterator begin() const; iterator end() const; }; template <> class Store : public DynamicStore { std::map mStatic; public: typedef std::map::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 : public DynamicStore { typedef std::unordered_map 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 mShared; mutable bool mKeywordSearchModFlag; mutable MWDialogue::KeywordSearch mKeywordSearch; public: Store(); typedef SharedIterator 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& list) const override; const MWDialogue::KeywordSearch& getDialogIdKeywordSearch() const; }; template <> class Store : public TypedDynamicStore { public: void preprocessReferences(const Store& cells); std::span getByCell(ESM::RefId cellId) const; private: std::unordered_map> mPerCellReferences; }; } // end namespace #endif