#include "cellstore.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwmechanics/creaturestats.hpp" #include "ptr.hpp" #include "esmstore.hpp" #include "class.hpp" #include "containerstore.hpp" namespace { template MWWorld::Ptr searchInContainerList (MWWorld::CellRefList& containerList, const std::string& id) { for (typename MWWorld::CellRefList::List::iterator iter (containerList.mList.begin()); iter!=containerList.mList.end(); ++iter) { MWWorld::Ptr container (&*iter, 0); MWWorld::Ptr ptr = container.getClass().getContainerStore (container).search (id); if (!ptr.isEmpty()) return ptr; } return MWWorld::Ptr(); } template MWWorld::Ptr searchViaActorId (MWWorld::CellRefList& actorList, int actorId, MWWorld::CellStore *cell) { for (typename MWWorld::CellRefList::List::iterator iter (actorList.mList.begin()); iter!=actorList.mList.end(); ++iter) { MWWorld::Ptr actor (&*iter, cell); if (actor.getClass().getCreatureStats (actor).matchesActorId (actorId) && actor.getRefData().getCount() > 0) return actor; } return MWWorld::Ptr(); } template void writeReferenceCollection (ESM::ESMWriter& writer, const MWWorld::CellRefList& collection) { if (!collection.mList.empty()) { // references for (typename MWWorld::CellRefList::List::const_iterator iter (collection.mList.begin()); iter!=collection.mList.end(); ++iter) { if (!iter->mData.hasChanged() && !iter->mRef.hasChanged() && iter->mRef.hasContentFile()) { // Reference that came from a content file and has not been changed -> ignore continue; } if (iter->mData.getCount()==0 && !iter->mRef.hasContentFile()) { // Deleted reference that did not come from a content file -> ignore continue; } RecordType state; iter->save (state); // recordId currently unused writer.writeHNT ("OBJE", collection.mList.front().mBase->sRecordId); state.save (writer); } } } template void readReferenceCollection (ESM::ESMReader& reader, MWWorld::CellRefList& collection, const ESM::CellRef& cref, const std::map& contentFileMap) { const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); RecordType state; state.mRef = cref; state.load(reader); // If the reference came from a content file, make sure this content file is loaded if (state.mRef.mRefNum.hasContentFile()) { std::map::const_iterator iter = contentFileMap.find (state.mRef.mRefNum.mContentFile); if (iter==contentFileMap.end()) return; // content file has been removed -> skip state.mRef.mRefNum.mContentFile = iter->second; } if (!MWWorld::LiveCellRef::checkState (state)) return; // not valid anymore with current content files -> skip const T *record = esmStore.get().search (state.mRef.mRefID); if (!record) return; if (state.mRef.mRefNum.hasContentFile()) { for (typename MWWorld::CellRefList::List::iterator iter (collection.mList.begin()); iter!=collection.mList.end(); ++iter) if (iter->mRef.getRefNum()==state.mRef.mRefNum) { // overwrite existing reference iter->load (state); return; } } // new reference MWWorld::LiveCellRef ref (record); ref.load (state); collection.mList.push_back (ref); } } namespace MWWorld { template void CellRefList::load(ESM::CellRef &ref, bool deleted, const MWWorld::ESMStore &esmStore) { const MWWorld::Store &store = esmStore.get(); if (const X *ptr = store.search (ref.mRefID)) { typename std::list::iterator iter = std::find(mList.begin(), mList.end(), ref.mRefNum); LiveRef liveCellRef (ref, ptr); if (deleted) liveCellRef.mData.setDeleted(true); if (iter != mList.end()) *iter = liveCellRef; else mList.push_back (liveCellRef); } else { std::cerr << "Error: could not resolve cell reference " << ref.mRefID << " (dropping reference)" << std::endl; } } template bool operator==(const LiveCellRef& ref, int pRefnum) { return (ref.mRef.mRefnum == pRefnum); } CellStore::CellStore (const ESM::Cell *cell) : mCell (cell), mState (State_Unloaded), mHasState (false), mLastRespawn(0,0) { mWaterLevel = cell->mWater; } const ESM::Cell *CellStore::getCell() const { return mCell; } CellStore::State CellStore::getState() const { return mState; } bool CellStore::hasState() const { return mHasState; } bool CellStore::hasId (const std::string& id) const { if (mState==State_Unloaded) return false; if (mState==State_Preloaded) return std::binary_search (mIds.begin(), mIds.end(), id); /// \todo address const-issues return const_cast (this)->search (id).isEmpty(); } Ptr CellStore::search (const std::string& id) { bool oldState = mHasState; mHasState = true; if (LiveCellRef *ref = mActivators.find (id)) return Ptr (ref, this); if (LiveCellRef *ref = mPotions.find (id)) return Ptr (ref, this); if (LiveCellRef *ref = mAppas.find (id)) return Ptr (ref, this); if (LiveCellRef *ref = mArmors.find (id)) return Ptr (ref, this); if (LiveCellRef *ref = mBooks.find (id)) return Ptr (ref, this); if (LiveCellRef *ref = mClothes.find (id)) return Ptr (ref, this); if (LiveCellRef *ref = mContainers.find (id)) return Ptr (ref, this); if (LiveCellRef *ref = mCreatures.find (id)) return Ptr (ref, this); if (LiveCellRef *ref = mDoors.find (id)) return Ptr (ref, this); if (LiveCellRef *ref = mIngreds.find (id)) return Ptr (ref, this); if (LiveCellRef *ref = mCreatureLists.find (id)) return Ptr (ref, this); if (LiveCellRef *ref = mItemLists.find (id)) return Ptr (ref, this); if (LiveCellRef *ref = mLights.find (id)) return Ptr (ref, this); if (LiveCellRef *ref = mLockpicks.find (id)) return Ptr (ref, this); if (LiveCellRef *ref = mMiscItems.find (id)) return Ptr (ref, this); if (LiveCellRef *ref = mNpcs.find (id)) return Ptr (ref, this); if (LiveCellRef *ref = mProbes.find (id)) return Ptr (ref, this); if (LiveCellRef *ref = mRepairs.find (id)) return Ptr (ref, this); if (LiveCellRef *ref = mStatics.find (id)) return Ptr (ref, this); if (LiveCellRef *ref = mWeapons.find (id)) return Ptr (ref, this); mHasState = oldState; return Ptr(); } Ptr CellStore::searchViaHandle (const std::string& handle) { bool oldState = mHasState; mHasState = true; if (LiveCellRef *ref = mActivators.searchViaHandle (handle)) return Ptr (ref, this); if (LiveCellRef *ref = mPotions.searchViaHandle (handle)) return Ptr (ref, this); if (LiveCellRef *ref = mAppas.searchViaHandle (handle)) return Ptr (ref, this); if (LiveCellRef *ref = mArmors.searchViaHandle (handle)) return Ptr (ref, this); if (LiveCellRef *ref = mBooks.searchViaHandle (handle)) return Ptr (ref, this); if (LiveCellRef *ref = mClothes.searchViaHandle (handle)) return Ptr (ref, this); if (LiveCellRef *ref = mContainers.searchViaHandle (handle)) return Ptr (ref, this); if (LiveCellRef *ref = mCreatures.searchViaHandle (handle)) return Ptr (ref, this); if (LiveCellRef *ref = mDoors.searchViaHandle (handle)) return Ptr (ref, this); if (LiveCellRef *ref = mIngreds.searchViaHandle (handle)) return Ptr (ref, this); if (LiveCellRef *ref = mCreatureLists.searchViaHandle (handle)) return Ptr (ref, this); if (LiveCellRef *ref = mItemLists.searchViaHandle (handle)) return Ptr (ref, this); if (LiveCellRef *ref = mLights.searchViaHandle (handle)) return Ptr (ref, this); if (LiveCellRef *ref = mLockpicks.searchViaHandle (handle)) return Ptr (ref, this); if (LiveCellRef *ref = mMiscItems.searchViaHandle (handle)) return Ptr (ref, this); if (LiveCellRef *ref = mNpcs.searchViaHandle (handle)) return Ptr (ref, this); if (LiveCellRef *ref = mProbes.searchViaHandle (handle)) return Ptr (ref, this); if (LiveCellRef *ref = mRepairs.searchViaHandle (handle)) return Ptr (ref, this); if (LiveCellRef *ref = mStatics.searchViaHandle (handle)) return Ptr (ref, this); if (LiveCellRef *ref = mWeapons.searchViaHandle (handle)) return Ptr (ref, this); mHasState = oldState; return Ptr(); } Ptr CellStore::searchViaActorId (int id) { if (Ptr ptr = ::searchViaActorId (mNpcs, id, this)) return ptr; if (Ptr ptr = ::searchViaActorId (mCreatures, id, this)) return ptr; return Ptr(); } float CellStore::getWaterLevel() const { return mWaterLevel; } void CellStore::setWaterLevel (float level) { mWaterLevel = level; mHasState = true; } int CellStore::count() const { return mActivators.mList.size() + mPotions.mList.size() + mAppas.mList.size() + mArmors.mList.size() + mBooks.mList.size() + mClothes.mList.size() + mContainers.mList.size() + mDoors.mList.size() + mIngreds.mList.size() + mCreatureLists.mList.size() + mItemLists.mList.size() + mLights.mList.size() + mLockpicks.mList.size() + mMiscItems.mList.size() + mProbes.mList.size() + mRepairs.mList.size() + mStatics.mList.size() + mWeapons.mList.size() + mCreatures.mList.size() + mNpcs.mList.size(); } void CellStore::load (const MWWorld::ESMStore &store, std::vector &esm) { if (mState!=State_Loaded) { if (mState==State_Preloaded) mIds.clear(); loadRefs (store, esm); mState = State_Loaded; // TODO: the pathgrid graph only needs to be loaded for active cells, so move this somewhere else. // In a simple test, loading the graph for all cells in MW + expansions took 200 ms mPathgridGraph.load(this); } } void CellStore::preload (const MWWorld::ESMStore &store, std::vector &esm) { if (mState==State_Unloaded) { listRefs (store, esm); mState = State_Preloaded; } } void CellStore::listRefs(const MWWorld::ESMStore &store, std::vector &esm) { assert (mCell); if (mCell->mContextList.empty()) return; // this is a dynamically generated cell -> skipping. // Load references from all plugins that do something with this cell. for (size_t i = 0; i < mCell->mContextList.size(); i++) { // Reopen the ESM reader and seek to the right position. int index = mCell->mContextList.at(i).index; mCell->restore (esm[index], i); ESM::CellRef ref; // Get each reference in turn bool deleted = false; while (mCell->getNextRef (esm[index], ref, deleted)) { if (deleted) continue; // Don't list reference if it was moved to a different cell. ESM::MovedCellRefTracker::const_iterator iter = std::find(mCell->mMovedRefs.begin(), mCell->mMovedRefs.end(), ref.mRefNum); if (iter != mCell->mMovedRefs.end()) { continue; } mIds.push_back (Misc::StringUtils::lowerCase (ref.mRefID)); } } // List moved references, from separately tracked list. for (ESM::CellRefTracker::const_iterator it = mCell->mLeasedRefs.begin(); it != mCell->mLeasedRefs.end(); ++it) { const ESM::CellRef &ref = *it; mIds.push_back(Misc::StringUtils::lowerCase(ref.mRefID)); } std::sort (mIds.begin(), mIds.end()); } void CellStore::loadRefs(const MWWorld::ESMStore &store, std::vector &esm) { assert (mCell); if (mCell->mContextList.empty()) return; // this is a dynamically generated cell -> skipping. // Load references from all plugins that do something with this cell. for (size_t i = 0; i < mCell->mContextList.size(); i++) { // Reopen the ESM reader and seek to the right position. int index = mCell->mContextList.at(i).index; mCell->restore (esm[index], i); ESM::CellRef ref; ref.mRefNum.mContentFile = ESM::RefNum::RefNum_NoContentFile; // Get each reference in turn bool deleted = false; while(mCell->getNextRef(esm[index], ref, deleted)) { // Don't load reference if it was moved to a different cell. ESM::MovedCellRefTracker::const_iterator iter = std::find(mCell->mMovedRefs.begin(), mCell->mMovedRefs.end(), ref.mRefNum); if (iter != mCell->mMovedRefs.end()) { continue; } loadRef (ref, deleted, store); } } // Load moved references, from separately tracked list. for (ESM::CellRefTracker::const_iterator it = mCell->mLeasedRefs.begin(); it != mCell->mLeasedRefs.end(); ++it) { ESM::CellRef &ref = const_cast(*it); loadRef (ref, false, store); } } bool CellStore::isExterior() const { return mCell->isExterior(); } Ptr CellStore::searchInContainer (const std::string& id) { bool oldState = mHasState; mHasState = true; if (Ptr ptr = searchInContainerList (mContainers, id)) return ptr; if (Ptr ptr = searchInContainerList (mCreatures, id)) return ptr; if (Ptr ptr = searchInContainerList (mNpcs, id)) return ptr; mHasState = oldState; return Ptr(); } void CellStore::loadRef (ESM::CellRef& ref, bool deleted, const ESMStore& store) { Misc::StringUtils::toLower (ref.mRefID); switch (store.find (ref.mRefID)) { case ESM::REC_ACTI: mActivators.load(ref, deleted, store); break; case ESM::REC_ALCH: mPotions.load(ref, deleted, store); break; case ESM::REC_APPA: mAppas.load(ref, deleted, store); break; case ESM::REC_ARMO: mArmors.load(ref, deleted, store); break; case ESM::REC_BOOK: mBooks.load(ref, deleted, store); break; case ESM::REC_CLOT: mClothes.load(ref, deleted, store); break; case ESM::REC_CONT: mContainers.load(ref, deleted, store); break; case ESM::REC_CREA: mCreatures.load(ref, deleted, store); break; case ESM::REC_DOOR: mDoors.load(ref, deleted, store); break; case ESM::REC_INGR: mIngreds.load(ref, deleted, store); break; case ESM::REC_LEVC: mCreatureLists.load(ref, deleted, store); break; case ESM::REC_LEVI: mItemLists.load(ref, deleted, store); break; case ESM::REC_LIGH: mLights.load(ref, deleted, store); break; case ESM::REC_LOCK: mLockpicks.load(ref, deleted, store); break; case ESM::REC_MISC: mMiscItems.load(ref, deleted, store); break; case ESM::REC_NPC_: mNpcs.load(ref, deleted, store); break; case ESM::REC_PROB: mProbes.load(ref, deleted, store); break; case ESM::REC_REPA: mRepairs.load(ref, deleted, store); break; case ESM::REC_STAT: mStatics.load(ref, deleted, store); break; case ESM::REC_WEAP: mWeapons.load(ref, deleted, store); break; case 0: std::cerr << "Cell reference " + ref.mRefID + " not found!\n"; break; default: std::cerr << "WARNING: Ignoring reference '" << ref.mRefID << "' of unhandled type\n"; } } void CellStore::loadState (const ESM::CellState& state) { mHasState = true; if (mCell->mData.mFlags & ESM::Cell::Interior && mCell->mData.mFlags & ESM::Cell::HasWater) mWaterLevel = state.mWaterLevel; mWaterLevel = state.mWaterLevel; mLastRespawn = MWWorld::TimeStamp(state.mLastRespawn); } void CellStore::saveState (ESM::CellState& state) const { state.mId = mCell->getCellId(); if (mCell->mData.mFlags & ESM::Cell::Interior && mCell->mData.mFlags & ESM::Cell::HasWater) state.mWaterLevel = mWaterLevel; state.mWaterLevel = mWaterLevel; state.mHasFogOfWar = (mFogState.get() ? 1 : 0); state.mLastRespawn = mLastRespawn.toEsm(); } void CellStore::writeFog(ESM::ESMWriter &writer) const { if (mFogState.get()) { mFogState->save(writer, mCell->mData.mFlags & ESM::Cell::Interior); } } void CellStore::readFog(ESM::ESMReader &reader) { mFogState.reset(new ESM::FogState()); mFogState->load(reader); } void CellStore::writeReferences (ESM::ESMWriter& writer) const { writeReferenceCollection (writer, mActivators); writeReferenceCollection (writer, mPotions); writeReferenceCollection (writer, mAppas); writeReferenceCollection (writer, mArmors); writeReferenceCollection (writer, mBooks); writeReferenceCollection (writer, mClothes); writeReferenceCollection (writer, mContainers); writeReferenceCollection (writer, mCreatures); writeReferenceCollection (writer, mDoors); writeReferenceCollection (writer, mIngreds); writeReferenceCollection (writer, mCreatureLists); writeReferenceCollection (writer, mItemLists); writeReferenceCollection (writer, mLights); writeReferenceCollection (writer, mLockpicks); writeReferenceCollection (writer, mMiscItems); writeReferenceCollection (writer, mNpcs); writeReferenceCollection (writer, mProbes); writeReferenceCollection (writer, mRepairs); writeReferenceCollection (writer, mStatics); writeReferenceCollection (writer, mWeapons); } void CellStore::readReferences (ESM::ESMReader& reader, const std::map& contentFileMap) { mHasState = true; while (reader.isNextSub ("OBJE")) { unsigned int unused; reader.getHT (unused); // load the RefID first so we know what type of object it is ESM::CellRef cref; cref.loadId(reader, true); int type = MWBase::Environment::get().getWorld()->getStore().find(cref.mRefID); if (type == 0) { std::cerr << "Dropping reference to '" << cref.mRefID << "' (object no longer exists)" << std::endl; reader.skipHSubUntil("OBJE"); continue; } switch (type) { case ESM::REC_ACTI: readReferenceCollection (reader, mActivators, cref, contentFileMap); break; case ESM::REC_ALCH: readReferenceCollection (reader, mPotions, cref, contentFileMap); break; case ESM::REC_APPA: readReferenceCollection (reader, mAppas, cref, contentFileMap); break; case ESM::REC_ARMO: readReferenceCollection (reader, mArmors, cref, contentFileMap); break; case ESM::REC_BOOK: readReferenceCollection (reader, mBooks, cref, contentFileMap); break; case ESM::REC_CLOT: readReferenceCollection (reader, mClothes, cref, contentFileMap); break; case ESM::REC_CONT: readReferenceCollection (reader, mContainers, cref, contentFileMap); break; case ESM::REC_CREA: readReferenceCollection (reader, mCreatures, cref, contentFileMap); break; case ESM::REC_DOOR: readReferenceCollection (reader, mDoors, cref, contentFileMap); break; case ESM::REC_INGR: readReferenceCollection (reader, mIngreds, cref, contentFileMap); break; case ESM::REC_LEVC: readReferenceCollection (reader, mCreatureLists, cref, contentFileMap); break; case ESM::REC_LEVI: readReferenceCollection (reader, mItemLists, cref, contentFileMap); break; case ESM::REC_LIGH: readReferenceCollection (reader, mLights, cref, contentFileMap); break; case ESM::REC_LOCK: readReferenceCollection (reader, mLockpicks, cref, contentFileMap); break; case ESM::REC_MISC: readReferenceCollection (reader, mMiscItems, cref, contentFileMap); break; case ESM::REC_NPC_: readReferenceCollection (reader, mNpcs, cref, contentFileMap); break; case ESM::REC_PROB: readReferenceCollection (reader, mProbes, cref, contentFileMap); break; case ESM::REC_REPA: readReferenceCollection (reader, mRepairs, cref, contentFileMap); break; case ESM::REC_STAT: readReferenceCollection (reader, mStatics, cref, contentFileMap); break; case ESM::REC_WEAP: readReferenceCollection (reader, mWeapons, cref, contentFileMap); break; default: throw std::runtime_error ("unknown type in cell reference section"); } } } bool operator== (const CellStore& left, const CellStore& right) { return left.getCell()->getCellId()==right.getCell()->getCellId(); } bool operator!= (const CellStore& left, const CellStore& right) { return !(left==right); } bool CellStore::isPointConnected(const int start, const int end) const { return mPathgridGraph.isPointConnected(start, end); } std::list CellStore::aStarSearch(const int start, const int end) const { return mPathgridGraph.aStarSearch(start, end); } void CellStore::setFog(ESM::FogState *fog) { mFogState.reset(fog); } ESM::FogState* CellStore::getFog() const { return mFogState.get(); } void CellStore::respawn() { if (mState == State_Loaded) { static int iMonthsToRespawn = MWBase::Environment::get().getWorld()->getStore().get().find("iMonthsToRespawn")->getInt(); if (MWBase::Environment::get().getWorld()->getTimeStamp() - mLastRespawn > 24*30*iMonthsToRespawn) { mLastRespawn = MWBase::Environment::get().getWorld()->getTimeStamp(); for (CellRefList::List::iterator it (mContainers.mList.begin()); it!=mContainers.mList.end(); ++it) { Ptr ptr (&*it, this); ptr.getClass().respawn(ptr); } for (CellRefList::List::iterator it (mCreatures.mList.begin()); it!=mCreatures.mList.end(); ++it) { Ptr ptr (&*it, this); ptr.getClass().respawn(ptr); } for (CellRefList::List::iterator it (mNpcs.mList.begin()); it!=mNpcs.mList.end(); ++it) { Ptr ptr (&*it, this); ptr.getClass().respawn(ptr); } for (CellRefList::List::iterator it (mCreatureLists.mList.begin()); it!=mCreatureLists.mList.end(); ++it) { Ptr ptr (&*it, this); ptr.getClass().respawn(ptr); } } } } }