#ifndef GAME_MWWORLD_CELLSTORE_H #define GAME_MWWORLD_CELLSTORE_H #include <algorithm> #include <stdexcept> #include <string> #include <typeinfo> #include <map> #include <memory> #include "livecellref.hpp" #include "cellreflist.hpp" #include <components/esm/loadacti.hpp> #include <components/esm/loadalch.hpp> #include <components/esm/loadappa.hpp> #include <components/esm/loadarmo.hpp> #include <components/esm/loadbook.hpp> #include <components/esm/loadclot.hpp> #include <components/esm/loadcont.hpp> #include <components/esm/loadcrea.hpp> #include <components/esm/loaddoor.hpp> #include <components/esm/loadingr.hpp> #include <components/esm/loadlevlist.hpp> #include <components/esm/loadligh.hpp> #include <components/esm/loadlock.hpp> #include <components/esm/loadprob.hpp> #include <components/esm/loadrepa.hpp> #include <components/esm/loadstat.hpp> #include <components/esm/loadweap.hpp> #include <components/esm/loadnpc.hpp> #include <components/esm/loadmisc.hpp> #include <components/esm/loadbody.hpp> #include "timestamp.hpp" #include "ptr.hpp" namespace ESM { struct Cell; struct CellState; struct FogState; struct CellId; struct RefNum; } namespace MWWorld { class ESMStore; /// \brief Mutable state of a cell class CellStore { public: enum State { State_Unloaded, State_Preloaded, State_Loaded }; private: const MWWorld::ESMStore& mStore; std::vector<ESM::ESMReader>& mReader; // 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::shared_ptr<ESM::FogState> mFogState; const ESM::Cell *mCell; State mState; bool mHasState; std::vector<std::string> mIds; float mWaterLevel; MWWorld::TimeStamp mLastRespawn; // List of refs owned by this cell CellRefList<ESM::Activator> mActivators; CellRefList<ESM::Potion> mPotions; CellRefList<ESM::Apparatus> mAppas; CellRefList<ESM::Armor> mArmors; CellRefList<ESM::Book> mBooks; CellRefList<ESM::Clothing> mClothes; CellRefList<ESM::Container> mContainers; CellRefList<ESM::Creature> mCreatures; CellRefList<ESM::Door> mDoors; CellRefList<ESM::Ingredient> mIngreds; CellRefList<ESM::CreatureLevList> mCreatureLists; CellRefList<ESM::ItemLevList> mItemLists; CellRefList<ESM::Light> mLights; CellRefList<ESM::Lockpick> mLockpicks; CellRefList<ESM::Miscellaneous> mMiscItems; CellRefList<ESM::NPC> mNpcs; CellRefList<ESM::Probe> mProbes; CellRefList<ESM::Repair> mRepairs; CellRefList<ESM::Static> mStatics; CellRefList<ESM::Weapon> mWeapons; CellRefList<ESM::BodyPart> mBodyParts; typedef std::map<LiveCellRefBase*, MWWorld::CellStore*> MovedRefTracker; // References owned by a different cell that have been moved here. // <reference, cell the reference originally came from> MovedRefTracker mMovedHere; // References owned by this cell that have been moved to another cell. // <reference, cell the reference was moved to> MovedRefTracker mMovedToAnotherCell; // Merged list of ref's currently in this cell - i.e. with added refs from mMovedHere, removed refs from mMovedToAnotherCell std::vector<LiveCellRefBase*> mMergedRefs; // 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 updateMergedRefs(); // (item, max charge) typedef std::vector<std::pair<LiveCellRefBase*, float> > TRechargingItems; TRechargingItems mRechargingItems; bool mRechargingItemsUpToDate; void updateRechargingItems(); void rechargeItems(float duration); void checkItem(Ptr ptr); // helper function for forEachInternal template<class Visitor, class List> bool forEachImp (Visitor& visitor, List& list) { for (typename List::List::iterator iter (list.mList.begin()); iter!=list.mList.end(); ++iter) { if (!isAccessible(iter->mData, iter->mRef)) continue; if (!visitor (MWWorld::Ptr(&*iter, this))) return false; } return true; } // listing only objects owned by this cell. Internal use only, you probably want to use forEach() so that moved objects are accounted for. template<class Visitor> bool forEachInternal (Visitor& visitor) { return forEachImp (visitor, mActivators) && forEachImp (visitor, mPotions) && forEachImp (visitor, mAppas) && forEachImp (visitor, mArmors) && forEachImp (visitor, mBooks) && forEachImp (visitor, mClothes) && forEachImp (visitor, mContainers) && forEachImp (visitor, mDoors) && forEachImp (visitor, mIngreds) && forEachImp (visitor, mItemLists) && forEachImp (visitor, mLights) && forEachImp (visitor, mLockpicks) && forEachImp (visitor, mMiscItems) && forEachImp (visitor, mProbes) && forEachImp (visitor, mRepairs) && forEachImp (visitor, mStatics) && forEachImp (visitor, mWeapons) && forEachImp (visitor, mBodyParts) && forEachImp (visitor, mCreatures) && forEachImp (visitor, mNpcs) && forEachImp (visitor, mCreatureLists); } /// @note If you get a linker error here, this means the given type can not be stored in a cell. The supported types are /// defined at the bottom of this file. template <class T> CellRefList<T>& get(); public: /// 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 <typename T> LiveCellRefBase* insert(const LiveCellRef<T>* ref) { mHasState = true; CellRefList<T>& list = get<T>(); LiveCellRefBase* ret = &list.insert(*ref); updateMergedRefs(); return ret; } /// @param readerList The readers to use for loading of the cell on-demand. CellStore (const ESM::Cell *cell_, const MWWorld::ESMStore& store, std::vector<ESM::ESMReader>& readerList); const ESM::Cell *getCell() const; State getState() const; const std::vector<std::string>& 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 std::string& 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 std::string& 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 std::string& 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. Ptr searchViaRefNum (const ESM::RefNum& refNum); ///< Will return an empty Ptr if cell is not loaded. Does not check references in /// containers. /// @note Triggers CellStore hasState flag. float getWaterLevel() const; bool movedHere(const MWWorld::Ptr& ptr) const; void setWaterLevel (float level); void setFog (ESM::FogState* 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<class Visitor> bool forEach (Visitor&& visitor) { if (mState != State_Loaded) return false; 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<class Visitor> bool forEachConst (Visitor&& visitor) const { if (mState != State_Loaded) return false; 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 <class T, class Visitor> bool forEachType(Visitor& visitor) { if (mState != State_Loaded) return false; if (mMergedRefs.empty()) return true; mHasState = true; CellRefList<T>& list = get<T>(); for (typename CellRefList<T>::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<LiveCellRef<T>*>(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<ESM::Door>& getReadOnlyDoors() const { return mDoors; } inline const CellRefList<ESM::Static>& getReadOnlyStatics() const { return mStatics; } bool isExterior() const; Ptr searchInContainer (const std::string& 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 { public: ///@note must return nullptr if the cell is not found virtual CellStore* getCellStore(const ESM::CellId& 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<int, int>& contentFileMap, GetCellStoreCallback* callback); void respawn (); ///< Check mLastRespawn and respawn references if necessary. This is a no-op if the cell is not loaded. private: /// Run through references and store IDs void listRefs(); void loadRefs(); void loadRef (ESM::CellRef& ref, bool deleted, std::map<ESM::RefNum, std::string>& refNumToID); ///< Make case-adjustments to \a ref and insert it into the respective container. /// /// Invalid \a ref objects are silently dropped. }; template<> inline CellRefList<ESM::Activator>& CellStore::get<ESM::Activator>() { mHasState = true; return mActivators; } template<> inline CellRefList<ESM::Potion>& CellStore::get<ESM::Potion>() { mHasState = true; return mPotions; } template<> inline CellRefList<ESM::Apparatus>& CellStore::get<ESM::Apparatus>() { mHasState = true; return mAppas; } template<> inline CellRefList<ESM::Armor>& CellStore::get<ESM::Armor>() { mHasState = true; return mArmors; } template<> inline CellRefList<ESM::Book>& CellStore::get<ESM::Book>() { mHasState = true; return mBooks; } template<> inline CellRefList<ESM::Clothing>& CellStore::get<ESM::Clothing>() { mHasState = true; return mClothes; } template<> inline CellRefList<ESM::Container>& CellStore::get<ESM::Container>() { mHasState = true; return mContainers; } template<> inline CellRefList<ESM::Creature>& CellStore::get<ESM::Creature>() { mHasState = true; return mCreatures; } template<> inline CellRefList<ESM::Door>& CellStore::get<ESM::Door>() { mHasState = true; return mDoors; } template<> inline CellRefList<ESM::Ingredient>& CellStore::get<ESM::Ingredient>() { mHasState = true; return mIngreds; } template<> inline CellRefList<ESM::CreatureLevList>& CellStore::get<ESM::CreatureLevList>() { mHasState = true; return mCreatureLists; } template<> inline CellRefList<ESM::ItemLevList>& CellStore::get<ESM::ItemLevList>() { mHasState = true; return mItemLists; } template<> inline CellRefList<ESM::Light>& CellStore::get<ESM::Light>() { mHasState = true; return mLights; } template<> inline CellRefList<ESM::Lockpick>& CellStore::get<ESM::Lockpick>() { mHasState = true; return mLockpicks; } template<> inline CellRefList<ESM::Miscellaneous>& CellStore::get<ESM::Miscellaneous>() { mHasState = true; return mMiscItems; } template<> inline CellRefList<ESM::NPC>& CellStore::get<ESM::NPC>() { mHasState = true; return mNpcs; } template<> inline CellRefList<ESM::Probe>& CellStore::get<ESM::Probe>() { mHasState = true; return mProbes; } template<> inline CellRefList<ESM::Repair>& CellStore::get<ESM::Repair>() { mHasState = true; return mRepairs; } template<> inline CellRefList<ESM::Static>& CellStore::get<ESM::Static>() { mHasState = true; return mStatics; } template<> inline CellRefList<ESM::Weapon>& CellStore::get<ESM::Weapon>() { mHasState = true; return mWeapons; } template<> inline CellRefList<ESM::BodyPart>& CellStore::get<ESM::BodyPart>() { mHasState = true; return mBodyParts; } bool operator== (const CellStore& left, const CellStore& right); bool operator!= (const CellStore& left, const CellStore& right); } #endif