#ifndef GAME_MWWORLD_CELLSTORE_H #define GAME_MWWORLD_CELLSTORE_H #include #include #include #include #include #include #include #include #include #include "cell.hpp" #include "cellreflist.hpp" #include "livecellref.hpp" #include #include #include #include "ptr.hpp" #include "timestamp.hpp" namespace ESM { class ReadersCache; struct Cell; struct CellState; struct FormId; using RefNum = FormId; struct Activator; struct Potion; struct Apparatus; struct Armor; struct Book; struct Clothing; struct Container; struct Creature; struct Door; struct Ingredient; struct CreatureLevList; struct ItemLevList; struct Light; struct Lockpick; struct Miscellaneous; struct NPC; struct Probe; struct Repair; struct Static; struct Weapon; struct BodyPart; struct CellCommon; } namespace ESM4 { class Reader; struct Cell; struct Reference; struct Static; struct Light; struct Activator; struct Potion; struct Ammunition; struct Armor; struct Book; struct Clothing; struct Container; struct Door; struct Furniture; struct Ingredient; struct MiscItem; struct Tree; struct Weapon; } namespace MWWorld { class ESMStore; struct CellStoreImp; using CellStoreTuple = std::tuple, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList>; /// \brief Mutable state of a cell class CellStore { public: enum State { State_Unloaded, State_Preloaded, State_Loaded }; /// Should this reference be accessible to the outside world (i.e. to scripts / game logic)? /// Determined based on the deletion flags. By default, objects deleted by content files are never accessible; /// objects deleted by setCount(0) are still accessible *if* they came from a content file (needed for vanilla /// scripting compatibility, and the fact that objects may be "un-deleted" in the original game). static bool isAccessible(const MWWorld::RefData& refdata, const MWWorld::CellRef& cref) { return !refdata.isDeletedByContentFile() && (cref.hasContentFile() || refdata.getCount() > 0); } /// Moves object from this cell to the given cell. /// @note automatically updates given cell by calling cellToMoveTo->moveFrom(...) /// @note throws exception if cellToMoveTo == this /// @return updated MWWorld::Ptr with the new CellStore pointer set. MWWorld::Ptr moveTo(const MWWorld::Ptr& object, MWWorld::CellStore* cellToMoveTo); void rest(double hours); void recharge(float duration); /// Make a copy of the given object and insert it into this cell. /// @note If you get a linker error here, this means the given type can not be inserted into a cell. /// The supported types are defined at the bottom of this file. template LiveCellRefBase* insert(const LiveCellRef* ref) { mHasState = true; CellRefList& list = get(); LiveCellRefBase* ret = &list.insert(*ref); requestMergedRefsUpdate(); return ret; } /// @param readerList The readers to use for loading of the cell on-demand. CellStore(MWWorld::Cell cell, const MWWorld::ESMStore& store, ESM::ReadersCache& readers); CellStore(const CellStore&) = delete; CellStore(CellStore&&) = delete; CellStore& operator=(const CellStore&) = delete; CellStore& operator=(CellStore&&) = delete; ~CellStore(); const MWWorld::Cell* getCell() const; State getState() const; const std::vector& getPreloadedIds() const; ///< Get Ids of objects in this cell, only valid in State_Preloaded bool hasState() const; ///< Does this cell have state that needs to be stored in a saved game file? bool hasId(const ESM::RefId& id) const; ///< May return true for deleted IDs when in preload state. Will return false, if cell is /// unloaded. /// @note Will not account for moved references which may exist in Loaded state. Use search() instead if the /// cell is loaded. Ptr search(const ESM::RefId& id); ///< Will return an empty Ptr if cell is not loaded. Does not check references in /// containers. /// @note Triggers CellStore hasState flag. ConstPtr searchConst(const ESM::RefId& id) const; ///< Will return an empty Ptr if cell is not loaded. Does not check references in /// containers. /// @note Does not trigger CellStore hasState flag. Ptr searchViaActorId(int id); ///< Will return an empty Ptr if cell is not loaded. float getWaterLevel() const; bool movedHere(const MWWorld::Ptr& ptr) const; void setWaterLevel(float level); void setFog(std::unique_ptr&& fog); ///< \note Takes ownership of the pointer ESM::FogState* getFog() const; std::size_t count() const; ///< Return total number of references, including deleted ones. void load(); ///< Load references from content file. void preload(); ///< Build ID list from content file. /// Call visitor (MWWorld::Ptr) for each reference. visitor must return a bool. Returning /// false will abort the iteration. /// \note Prefer using forEachConst when possible. /// \note Do not modify this cell (i.e. remove/add objects) during the forEach, doing this may result in /// unintended behaviour. \attention This function also lists deleted (count 0) objects! \return Iteration /// completed? template bool forEach(Visitor&& visitor) { if (mState != State_Loaded) return false; if (mMergedRefsNeedsUpdate) updateMergedRefs(); if (mMergedRefs.empty()) return true; mHasState = true; for (unsigned int i = 0; i < mMergedRefs.size(); ++i) { if (!isAccessible(mMergedRefs[i]->mData, mMergedRefs[i]->mRef)) continue; if (!visitor(MWWorld::Ptr(mMergedRefs[i], this))) return false; } return true; } /// Call visitor (MWWorld::ConstPtr) for each reference. visitor must return a bool. Returning /// false will abort the iteration. /// \note Do not modify this cell (i.e. remove/add objects) during the forEach, doing this may result in /// unintended behaviour. \attention This function also lists deleted (count 0) objects! \return Iteration /// completed? template bool forEachConst(Visitor&& visitor) const { if (mState != State_Loaded) return false; if (mMergedRefsNeedsUpdate) updateMergedRefs(); for (unsigned int i = 0; i < mMergedRefs.size(); ++i) { if (!isAccessible(mMergedRefs[i]->mData, mMergedRefs[i]->mRef)) continue; if (!visitor(MWWorld::ConstPtr(mMergedRefs[i], this))) return false; } return true; } /// Call visitor (ref) for each reference of given type. visitor must return a bool. Returning /// false will abort the iteration. /// \note Do not modify this cell (i.e. remove/add objects) during the forEach, doing this may result in /// unintended behaviour. \attention This function also lists deleted (count 0) objects! \return Iteration /// completed? template bool forEachType(Visitor& visitor) { if (mState != State_Loaded) return false; if (mMergedRefsNeedsUpdate) updateMergedRefs(); if (mMergedRefs.empty()) return true; mHasState = true; CellRefList& list = get(); for (typename CellRefList::List::iterator it(list.mList.begin()); it != list.mList.end(); ++it) { LiveCellRefBase* base = &*it; if (mMovedToAnotherCell.find(base) != mMovedToAnotherCell.end()) continue; if (!isAccessible(base->mData, base->mRef)) continue; if (!visitor(MWWorld::Ptr(base, this))) return false; } for (MovedRefTracker::const_iterator it = mMovedHere.begin(); it != mMovedHere.end(); ++it) { LiveCellRefBase* base = it->first; if (dynamic_cast*>(base)) if (!visitor(MWWorld::Ptr(base, this))) return false; } return true; } // NOTE: does not account for moved references // Should be phased out when we have const version of forEach inline const CellRefList& getReadOnlyDoors() const { return get(); } inline const CellRefList& getReadOnlyEsm4Doors() const { return get(); } inline const CellRefList& getReadOnlyStatics() const { return get(); } inline const CellRefList& getReadOnlyEsm4Statics() const { return get(); } bool isExterior() const; bool isQuasiExterior() const; Ptr searchInContainer(const ESM::RefId& id); void loadState(const ESM::CellState& state); void saveState(ESM::CellState& state) const; void writeFog(ESM::ESMWriter& writer) const; void readFog(ESM::ESMReader& reader); void writeReferences(ESM::ESMWriter& writer) const; struct GetCellStoreCallback { ///@note must return nullptr if the cell is not found virtual CellStore* getCellStore(const ESM::RefId& cellId) = 0; virtual ~GetCellStoreCallback() = default; }; /// @param callback to use for retrieving of additional CellStore objects by ID (required for resolving moved /// references) void readReferences( ESM::ESMReader& reader, const std::map& contentFileMap, GetCellStoreCallback* callback); void respawn(); ///< Check mLastRespawn and respawn references if necessary. This is a no-op if the cell is not loaded. Ptr getMovedActor(int actorId) const; Ptr getPtr(ESM::RefId id); private: friend struct CellStoreImp; const MWWorld::ESMStore& mStore; ESM::ReadersCache& mReaders; // Even though fog actually belongs to the player and not cells, // it makes sense to store it here since we need it once for each cell. // Note this is nullptr until the cell is explored to save some memory std::unique_ptr mFogState; MWWorld::Cell mCellVariant; State mState; bool mHasState; std::vector mIds; float mWaterLevel; MWWorld::TimeStamp mLastRespawn; template static constexpr std::size_t getTypeIndex() { static_assert(Misc::TupleHasType, CellStoreTuple>::value); return Misc::TupleTypeIndex, CellStoreTuple>::value; } std::unique_ptr mCellStoreImp; std::vector mCellRefLists; template CellRefList& get() { mHasState = true; return static_cast&>(*mCellRefLists[getTypeIndex()]); } template const CellRefList& get() const { return static_cast&>(*mCellRefLists[getTypeIndex()]); } typedef std::map MovedRefTracker; // References owned by a different cell that have been moved here. // MovedRefTracker mMovedHere; // References owned by this cell that have been moved to another cell. // MovedRefTracker mMovedToAnotherCell; // Merged list of ref's currently in this cell - i.e. with added refs from mMovedHere, removed refs from // mMovedToAnotherCell mutable std::vector mMergedRefs; mutable bool mMergedRefsNeedsUpdate = false; // Get the Ptr for the given ref which originated from this cell (possibly moved to another cell at this point). Ptr getCurrentPtr(MWWorld::LiveCellRefBase* ref); /// Moves object from the given cell to this cell. void moveFrom(const MWWorld::Ptr& object, MWWorld::CellStore* from); /// Repopulate mMergedRefs. void requestMergedRefsUpdate(); void updateMergedRefs() const; // (item, max charge) typedef std::vector> TRechargingItems; TRechargingItems mRechargingItems; bool mRechargingItemsUpToDate; void updateRechargingItems(); void rechargeItems(float duration); void checkItem(const Ptr& ptr); /// Run through references and store IDs void listRefs(const ESM::Cell& cell); void listRefs(const ESM4::Cell& cell); void listRefs(); void loadRefs(const ESM::Cell& cell, std::map& refNumToID); void loadRefs(const ESM4::Cell& cell, std::map& refNumToID); void loadRefs(); void loadRef(const ESM4::Reference& ref, bool deleted); void loadRef(ESM::CellRef& ref, bool deleted, std::map& refNumToID); ///< Make case-adjustments to \a ref and insert it into the respective container. /// /// Invalid \a ref objects are silently dropped. /// }; } #endif