#include "cellstore.hpp" #include "magiceffects.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/world.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/recharge.hpp" #include "class.hpp" #include "containerstore.hpp" #include "esmstore.hpp" #include "inventorystore.hpp" #include "ptr.hpp" #include "worldmodel.hpp" namespace { template struct RecordToState { using StateType = ESM::ObjectState; }; template <> struct RecordToState { using StateType = ESM::NpcState; }; template <> struct RecordToState { using StateType = ESM::CreatureState; }; template <> struct RecordToState { using StateType = ESM::DoorState; }; template <> struct RecordToState { using StateType = ESM::ContainerState; }; template <> struct RecordToState { using StateType = ESM::CreatureLevListState; }; template MWWorld::Ptr searchInContainerList(MWWorld::CellRefList& containerList, const ESM::RefId& id) { for (auto iter(containerList.mList.begin()); iter != containerList.mList.end(); ++iter) { MWWorld::Ptr container(&*iter, nullptr); if (container.getRefData().getCustomData() == nullptr) continue; 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, const std::map& toIgnore) { for (typename MWWorld::CellRefList::List::iterator iter(actorList.mList.begin()); iter != actorList.mList.end(); ++iter) { MWWorld::Ptr actor(&*iter, cell); if (toIgnore.find(&*iter) != toIgnore.end()) continue; 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; } using StateType = typename RecordToState::StateType; StateType state; iter->save(state); // recordId currently unused writer.writeHNT("OBJE", collection.mList.front().mBase->sRecordId); state.save(writer); } } } template void fixRestockingImpl(const T* base, RecordType& state) { // Workaround for old saves not containing negative quantities for (const auto& baseItem : base->mInventory.mList) { if (baseItem.mCount < 0) { for (auto& item : state.mInventory.mItems) { if (item.mCount > 0 && baseItem.mItem == item.mRef.mRefID) item.mCount = -item.mCount; } } } } template void fixRestocking(const T* base, RecordType& state) { } template <> void fixRestocking<>(const ESM::Creature* base, ESM::CreatureState& state) { fixRestockingImpl(base, state); } template <> void fixRestocking<>(const ESM::NPC* base, ESM::NpcState& state) { fixRestockingImpl(base, state); } template <> void fixRestocking<>(const ESM::Container* base, ESM::ContainerState& state) { fixRestockingImpl(base, state); } template void readReferenceCollection(ESM::ESMReader& reader, MWWorld::CellRefList& collection, const ESM::CellRef& cref, const std::map& contentFileMap, const MWWorld::ESMStore& esmStore, MWWorld::CellStore* cellstore) { using StateType = typename RecordToState::StateType; StateType 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.mVersion < 15) fixRestocking(record, state); if (state.mVersion < 17) { if constexpr (std::is_same_v) MWWorld::convertMagicEffects(state.mCreatureStats, state.mInventory); else if constexpr (std::is_same_v) MWWorld::convertMagicEffects(state.mCreatureStats, state.mInventory, &state.mNpcStats); } else if (state.mVersion < 20) { if constexpr (std::is_same_v || std::is_same_v) MWWorld::convertStats(state.mCreatureStats); } 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 && iter->mRef.getRefId() == state.mRef.mRefID) { // overwrite existing reference float oldscale = iter->mRef.getScale(); iter->load(state); const ESM::Position& oldpos = iter->mRef.getPosition(); const ESM::Position& newpos = iter->mData.getPosition(); const MWWorld::Ptr ptr(&*iter, cellstore); if ((oldscale != iter->mRef.getScale() || oldpos.asVec3() != newpos.asVec3() || oldpos.rot[0] != newpos.rot[0] || oldpos.rot[1] != newpos.rot[1] || oldpos.rot[2] != newpos.rot[2]) && !ptr.getClass().isActor()) MWBase::Environment::get().getWorld()->moveObject(ptr, newpos.asVec3()); if (!iter->mData.isEnabled()) { iter->mData.enable(); MWBase::Environment::get().getWorld()->disable(ptr); } MWBase::Environment::get().getWorldModel()->registerPtr(ptr); return; } Log(Debug::Warning) << "Warning: Dropping reference to " << state.mRef.mRefID << " (invalid content file link)"; return; } // new reference MWWorld::LiveCellRef ref(record); ref.load(state); collection.mList.push_back(ref); MWWorld::LiveCellRefBase* base = &collection.mList.back(); MWBase::Environment::get().getWorldModel()->registerPtr(MWWorld::Ptr(base, cellstore)); } // this function allows us to link a CellRefList to the associated recNameInt, and apply a function template static void recNameSwitcher(MWWorld::CellRefList& store, ESM::RecNameInts recnNameInt, Callable&& f) { if (RecordType::sRecordId == recnNameInt) { f(store); } } // helper function for forEachInternal template bool forEachImp(Visitor& visitor, List& list, MWWorld::CellStore* cellStore) { for (typename List::List::iterator iter(list.mList.begin()); iter != list.mList.end(); ++iter) { if (!MWWorld::CellStore::isAccessible(iter->mData, iter->mRef)) continue; if (!visitor(MWWorld::Ptr(&*iter, cellStore))) return false; } return true; } } namespace MWWorld { struct CellStoreImp { CellStoreTuple mRefLists; template static void assignStoreToIndex(CellStore& stores, CellRefList& refList) { const std::size_t storeIndex = CellStore::getTypeIndex(); if (stores.mCellRefLists.size() <= storeIndex) stores.mCellRefLists.resize(storeIndex + 1); assert(&refList == &std::get>(stores.mCellStoreImp->mRefLists)); stores.mCellRefLists[storeIndex] = &refList; } // listing only objects owned by this cell. Internal use only, you probably want to use forEach() so that moved // objects are accounted for. template static bool forEachInternal(Visitor& visitor, MWWorld::CellStore& cellStore) { bool returnValue = true; Misc::tupleForEach(cellStore.mCellStoreImp->mRefLists, [&visitor, &returnValue, &cellStore](auto& store) { returnValue = returnValue && forEachImp(visitor, store, &cellStore); }); return returnValue; } }; 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.setDeletedByContentFile(true); if (iter != mList.end()) *iter = liveCellRef; else mList.push_back(liveCellRef); } else { Log(Debug::Warning) << "Warning: could not resolve cell reference " << ref.mRefID << " (dropping reference)"; } } template void CellRefList::load(const ESM4::Reference& ref, bool deleted, const MWWorld::ESMStore& esmStore) { if constexpr (!ESM::isESM4Rec(X::sRecordId)) return; const MWWorld::Store& store = esmStore.get(); if (const X* ptr = store.search(ref.mBaseObj)) { LiveRef liveCellRef(ref, ptr); if (deleted) liveCellRef.mData.setDeletedByContentFile(true); mList.push_back(liveCellRef); } else { Log(Debug::Warning) << "Warning: could not resolve cell reference " << ref.mId << " (dropping reference)"; } } template bool operator==(const LiveCellRef& ref, int pRefnum) { return (ref.mRef.mRefnum == pRefnum); } Ptr CellStore::getCurrentPtr(LiveCellRefBase* ref) { MovedRefTracker::iterator found = mMovedToAnotherCell.find(ref); if (found != mMovedToAnotherCell.end()) return Ptr(ref, found->second); return Ptr(ref, this); } void CellStore::moveFrom(const Ptr& object, CellStore* from) { if (mState != State_Loaded) load(); mHasState = true; MovedRefTracker::iterator found = mMovedToAnotherCell.find(object.getBase()); if (found != mMovedToAnotherCell.end()) { // A cell we had previously moved an object to is returning it to us. assert(found->second == from); mMovedToAnotherCell.erase(found); } else { mMovedHere.insert(std::make_pair(object.getBase(), from)); } requestMergedRefsUpdate(); } MWWorld::Ptr CellStore::moveTo(const Ptr& object, CellStore* cellToMoveTo) { if (cellToMoveTo == this) throw std::runtime_error("moveTo: object is already in this cell"); // We assume that *this is in State_Loaded since we could hardly have reference to a live object otherwise. if (mState != State_Loaded) throw std::runtime_error( "moveTo: can't move object from a non-loaded cell (how did you get this object anyway?)"); MWBase::Environment::get().getWorldModel()->registerPtr(MWWorld::Ptr(object.getBase(), cellToMoveTo)); MovedRefTracker::iterator found = mMovedHere.find(object.getBase()); if (found != mMovedHere.end()) { // Special case - object didn't originate in this cell // Move it back to its original cell first CellStore* originalCell = found->second; assert(originalCell != this); originalCell->moveFrom(object, this); mMovedHere.erase(found); // Now that object is back to its rightful owner, we can move it if (cellToMoveTo != originalCell) { originalCell->moveTo(object, cellToMoveTo); } requestMergedRefsUpdate(); return MWWorld::Ptr(object.getBase(), cellToMoveTo); } cellToMoveTo->moveFrom(object, this); mMovedToAnotherCell.insert(std::make_pair(object.getBase(), cellToMoveTo)); requestMergedRefsUpdate(); MWWorld::Ptr ptr(object.getBase(), cellToMoveTo); const Class& cls = ptr.getClass(); if (cls.hasInventoryStore(ptr)) cls.getInventoryStore(ptr).setActor(ptr); return ptr; } struct MergeVisitor { MergeVisitor(std::vector& mergeTo, const std::map& movedHere, const std::map& movedToAnotherCell) : mMergeTo(mergeTo) , mMovedHere(movedHere) , mMovedToAnotherCell(movedToAnotherCell) { } bool operator()(const MWWorld::Ptr& ptr) { if (mMovedToAnotherCell.find(ptr.getBase()) != mMovedToAnotherCell.end()) return true; mMergeTo.push_back(ptr.getBase()); return true; } void merge() { for (const auto& [base, _] : mMovedHere) mMergeTo.push_back(base); } private: std::vector& mMergeTo; const std::map& mMovedHere; const std::map& mMovedToAnotherCell; }; void CellStore::requestMergedRefsUpdate() { mRechargingItemsUpToDate = false; mMergedRefsNeedsUpdate = true; } void CellStore::updateMergedRefs() const { mMergedRefs.clear(); MergeVisitor visitor(mMergedRefs, mMovedHere, mMovedToAnotherCell); CellStoreImp::forEachInternal(visitor, const_cast(*this)); visitor.merge(); mMergedRefsNeedsUpdate = false; } bool CellStore::movedHere(const MWWorld::Ptr& ptr) const { if (ptr.isEmpty()) return false; if (mMovedHere.find(ptr.getBase()) != mMovedHere.end()) return true; return false; } CellStore::CellStore(MWWorld::Cell cell, const MWWorld::ESMStore& esmStore, ESM::ReadersCache& readers) : mStore(esmStore) , mReaders(readers) , mCellVariant(std::move(cell)) , mState(State_Unloaded) , mHasState(false) , mLastRespawn(0, 0) , mCellStoreImp(std::make_unique()) , mRechargingItemsUpToDate(false) { std::apply([this](auto&... x) { (CellStoreImp::assignStoreToIndex(*this, x), ...); }, mCellStoreImp->mRefLists); mWaterLevel = mCellVariant.getWaterHeight(); } CellStore::~CellStore() = default; const MWWorld::Cell* CellStore::getCell() const { return &mCellVariant; } CellStore::State CellStore::getState() const { return mState; } const std::vector& CellStore::getPreloadedIds() const { return mIds; } bool CellStore::hasState() const { return mHasState; } bool CellStore::hasId(const ESM::RefId& id) const { if (mState == State_Unloaded) return false; if (mState == State_Preloaded) return std::binary_search(mIds.begin(), mIds.end(), id); return searchConst(id).isEmpty(); } template struct SearchVisitor { PtrType mFound; const ESM::RefId& mIdToFind; SearchVisitor(const ESM::RefId& id) : mIdToFind(id) { } bool operator()(const PtrType& ptr) { if (ptr.getCellRef().getRefId() == mIdToFind) { mFound = ptr; return false; } return true; } }; Ptr CellStore::search(const ESM::RefId& id) { SearchVisitor searchVisitor(id); forEach(searchVisitor); return searchVisitor.mFound; } ConstPtr CellStore::searchConst(const ESM::RefId& id) const { SearchVisitor searchVisitor(id); forEachConst(searchVisitor); return searchVisitor.mFound; } Ptr CellStore::searchViaActorId(int id) { if (Ptr ptr = ::searchViaActorId(get(), id, this, mMovedToAnotherCell); !ptr.isEmpty()) return ptr; if (Ptr ptr = ::searchViaActorId(get(), id, this, mMovedToAnotherCell); !ptr.isEmpty()) return ptr; for (const auto& [base, _] : mMovedHere) { MWWorld::Ptr actor(base, this); if (!actor.getClass().isActor()) continue; if (actor.getClass().getCreatureStats(actor).matchesActorId(id) && actor.getRefData().getCount() > 0) return actor; } return Ptr(); } class RefNumSearchVisitor { const ESM::RefNum& mRefNum; public: RefNumSearchVisitor(const ESM::RefNum& refNum) : mRefNum(refNum) { } Ptr mFound; bool operator()(const Ptr& ptr) { if (ptr.getCellRef().getRefNum() == mRefNum) { mFound = ptr; return false; } return true; } }; float CellStore::getWaterLevel() const { if (isExterior()) return getCell()->getWaterHeight(); return mWaterLevel; } void CellStore::setWaterLevel(float level) { mWaterLevel = level; mHasState = true; } std::size_t CellStore::count() const { if (mMergedRefsNeedsUpdate) updateMergedRefs(); return mMergedRefs.size(); } void CellStore::load() { if (mState != State_Loaded) { if (mState == State_Preloaded) mIds.clear(); loadRefs(); mState = State_Loaded; } } void CellStore::preload() { if (mState == State_Unloaded) { listRefs(); mState = State_Preloaded; } } void CellStore::listRefs(const ESM::Cell& cell) { if (cell.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 < cell.mContextList.size(); i++) { try { // Reopen the ESM reader and seek to the right position. const std::size_t index = static_cast(cell.mContextList[i].index); const ESM::ReadersCache::BusyItem reader = mReaders.get(index); cell.restore(*reader, i); ESM::CellRef ref; // Get each reference in turn ESM::MovedCellRef cMRef; bool deleted = false; bool moved = false; while (ESM::Cell::getNextRef( *reader, ref, deleted, cMRef, moved, ESM::Cell::GetNextRefMode::LoadOnlyNotMoved)) { if (deleted || moved) continue; // Don't list reference if it was moved to a different cell. ESM::MovedCellRefTracker::const_iterator iter = std::find(cell.mMovedRefs.begin(), cell.mMovedRefs.end(), ref.mRefNum); if (iter != cell.mMovedRefs.end()) { continue; } mIds.push_back(std::move(ref.mRefID)); } } catch (std::exception& e) { Log(Debug::Error) << "An error occurred listing references for cell " << getCell()->getDescription() << ": " << e.what(); } } // List moved references, from separately tracked list. for (const auto& [ref, deleted] : cell.mLeasedRefs) { if (!deleted) mIds.push_back(ref.mRefID); } } template static void visitCell4References( const ESM4::Cell& cell, const ESMStore& esmStore, ESM::ReadersCache& readers, ReferenceInvocable&& invocable) { for (const ESM4::Reference* ref : esmStore.get().getByCell(cell.mId)) invocable(*ref); } void CellStore::listRefs(const ESM4::Cell& cell) { visitCell4References(cell, mStore, mReaders, [&](const ESM4::Reference& ref) { mIds.push_back(ref.mBaseObj); }); } void CellStore::listRefs() { ESM::visit([&](auto&& cell) { listRefs(cell); }, mCellVariant); std::sort(mIds.begin(), mIds.end()); } void CellStore::loadRefs(const ESM::Cell& cell, std::map& refNumToID) { if (cell.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 < cell.mContextList.size(); i++) { try { // Reopen the ESM reader and seek to the right position. const std::size_t index = static_cast(cell.mContextList[i].index); const ESM::ReadersCache::BusyItem reader = mReaders.get(index); cell.restore(*reader, i); ESM::CellRef ref; // Get each reference in turn ESM::MovedCellRef cMRef; bool deleted = false; bool moved = false; while (ESM::Cell::getNextRef( *reader, ref, deleted, cMRef, moved, ESM::Cell::GetNextRefMode::LoadOnlyNotMoved)) { if (moved) continue; // Don't load reference if it was moved to a different cell. ESM::MovedCellRefTracker::const_iterator iter = std::find(cell.mMovedRefs.begin(), cell.mMovedRefs.end(), ref.mRefNum); if (iter != cell.mMovedRefs.end()) { continue; } loadRef(ref, deleted, refNumToID); } } catch (std::exception& e) { Log(Debug::Error) << "An error occurred loading references for cell " << getCell()->getDescription() << ": " << e.what(); } } // Load moved references, from separately tracked list. for (const auto& leasedRef : cell.mLeasedRefs) { ESM::CellRef& ref = const_cast(leasedRef.first); bool deleted = leasedRef.second; loadRef(ref, deleted, refNumToID); } } void CellStore::loadRefs(const ESM4::Cell& cell, std::map& refNumToID) { visitCell4References(cell, mStore, mReaders, [&](const ESM4::Reference& ref) { loadRef(ref, false); }); } void CellStore::loadRefs() { std::map refNumToID; // used to detect refID modifications ESM::visit([&](auto&& cell) { loadRefs(cell, refNumToID); }, mCellVariant); requestMergedRefsUpdate(); } bool CellStore::isExterior() const { return mCellVariant.isExterior(); } bool CellStore::isQuasiExterior() const { return mCellVariant.isQuasiExterior(); } Ptr CellStore::searchInContainer(const ESM::RefId& id) { bool oldState = mHasState; mHasState = true; if (Ptr ptr = searchInContainerList(get(), id); !ptr.isEmpty()) return ptr; if (Ptr ptr = searchInContainerList(get(), id); !ptr.isEmpty()) return ptr; if (Ptr ptr = searchInContainerList(get(), id); !ptr.isEmpty()) return ptr; mHasState = oldState; return Ptr(); } void CellStore::loadRef(const ESM4::Reference& ref, bool deleted) { const MWWorld::ESMStore& store = mStore; ESM::RecNameInts foundType = static_cast(store.find(ref.mBaseObj)); Misc::tupleForEach(this->mCellStoreImp->mRefLists, [&ref, &deleted, &store, foundType](auto& x) { recNameSwitcher( x, foundType, [&ref, &deleted, &store](auto& storeIn) { storeIn.load(ref, deleted, store); }); }); } void CellStore::loadRef(ESM::CellRef& ref, bool deleted, std::map& refNumToID) { const MWWorld::ESMStore& store = mStore; auto it = refNumToID.find(ref.mRefNum); if (it != refNumToID.end()) { if (it->second != ref.mRefID) { // refID was modified, make sure we don't end up with duplicated refs ESM::RecNameInts foundType = static_cast(store.find(it->second)); if (foundType != 0) { Misc::tupleForEach(this->mCellStoreImp->mRefLists, [&ref, foundType](auto& x) { recNameSwitcher(x, foundType, [&ref](auto& storeIn) { storeIn.remove(ref.mRefNum); }); }); } } } ESM::RecNameInts foundType = static_cast(store.find(ref.mRefID)); bool handledType = false; if (foundType != 0) { Misc::tupleForEach( this->mCellStoreImp->mRefLists, [&ref, &deleted, &store, foundType, &handledType](auto& x) { recNameSwitcher(x, foundType, [&ref, &deleted, &store, &handledType](auto& storeIn) { handledType = true; storeIn.load(ref, deleted, store); }); }); } else { Log(Debug::Error) << "Cell reference " << ref.mRefID << " is not found!"; return; } if (!handledType) { Log(Debug::Error) << "Error: Ignoring reference " << ref.mRefID << " of unhandled type"; return; } refNumToID[ref.mRefNum] = ref.mRefID; } void CellStore::loadState(const ESM::CellState& state) { mHasState = true; if (!mCellVariant.isExterior() && mCellVariant.hasWater()) mWaterLevel = state.mWaterLevel; mLastRespawn = MWWorld::TimeStamp(state.mLastRespawn); } void CellStore::saveState(ESM::CellState& state) const { state.mId = mCellVariant.getId(); if (!mCellVariant.isExterior() && mCellVariant.hasWater()) state.mWaterLevel = mWaterLevel; state.mIsInterior = !mCellVariant.isExterior(); state.mHasFogOfWar = (mFogState.get() ? 1 : 0); state.mLastRespawn = mLastRespawn.toEsm(); } void CellStore::writeFog(ESM::ESMWriter& writer) const { if (mFogState.get()) { mFogState->save(writer, !mCellVariant.isExterior()); } } void CellStore::readFog(ESM::ESMReader& reader) { mFogState = std::make_unique(); mFogState->load(reader); } void CellStore::writeReferences(ESM::ESMWriter& writer) const { Misc::tupleForEach(this->mCellStoreImp->mRefLists, [&writer](auto& cellRefList) { writeReferenceCollection(writer, cellRefList); }); for (const auto& [base, store] : mMovedToAnotherCell) { ESM::RefNum refNum = base->mRef.getRefNum(); ESM::RefId movedTo = store->getCell()->getId(); writer.writeFormId(refNum, true, "MVRF"); writer.writeCellId(movedTo); } } void CellStore::readReferences( ESM::ESMReader& reader, const std::map& contentFileMap, GetCellStoreCallback* callback) { 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 = mStore.find(cref.mRefID); if (type == 0) { Log(Debug::Warning) << "Dropping reference to '" << cref.mRefID << "' (object no longer exists)"; // Skip until the next OBJE or MVRF while (reader.hasMoreSubs() && !reader.peekNextSub("OBJE") && !reader.peekNextSub("MVRF")) { reader.getSubName(); reader.skipHSub(); } continue; } if (type != 0) { bool foundCorrespondingStore = false; Misc::tupleForEach(this->mCellStoreImp->mRefLists, [&reader, this, &cref, &contentFileMap, &foundCorrespondingStore, type](auto&& x) { recNameSwitcher(x, static_cast(type), [&reader, this, &cref, &contentFileMap, &foundCorrespondingStore](auto& store) { foundCorrespondingStore = true; readReferenceCollection(reader, store, cref, contentFileMap, mStore, this); }); }); if (!foundCorrespondingStore) throw std::runtime_error("unknown type in cell reference section"); } } while (reader.isNextSub("MVRF")) { reader.cacheSubName(); ESM::RefNum refnum = reader.getFormId(true, "MVRF"); ESM::RefId movedToId = reader.getCellId(); if (refnum.hasContentFile()) { auto iter = contentFileMap.find(refnum.mContentFile); if (iter != contentFileMap.end()) refnum.mContentFile = iter->second; } // Search for the reference. It might no longer exist if its content file was removed. Ptr movedRef = MWBase::Environment::get().getWorldModel()->getPtr(refnum); if (movedRef.isEmpty()) { Log(Debug::Warning) << "Warning: Dropping moved ref tag for " << refnum.mIndex << " (moved object no longer exists)"; continue; } CellStore* otherCell = callback->getCellStore(movedToId); if (otherCell == nullptr) { Log(Debug::Warning) << "Warning: Dropping moved ref tag for " << movedRef.getCellRef().getRefId() << " (target cell " << movedToId << " no longer exists). Reference moved back to its original location."; // Note by dropping tag the object will automatically re-appear in its original cell, though // potentially at inapproriate coordinates. Restore original coordinates: movedRef.getRefData().setPosition(movedRef.getCellRef().getPosition()); continue; } if (otherCell == this) { // Should never happen unless someone's tampering with files. Log(Debug::Warning) << "Found invalid moved ref, ignoring"; continue; } moveTo(movedRef, otherCell); } requestMergedRefsUpdate(); } void CellStore::setFog(std::unique_ptr&& fog) { mFogState = std::move(fog); } ESM::FogState* CellStore::getFog() const { return mFogState.get(); } static void clearCorpse(const MWWorld::Ptr& ptr, const ESMStore& store) { const MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr); static const float fCorpseClearDelay = store.get().find("fCorpseClearDelay")->mValue.getFloat(); if (creatureStats.isDead() && creatureStats.isDeathAnimationFinished() && !ptr.getClass().isPersistent(ptr) && creatureStats.getTimeOfDeath() + fCorpseClearDelay <= MWBase::Environment::get().getWorld()->getTimeStamp()) { MWBase::Environment::get().getWorld()->deleteObject(ptr); } } void CellStore::rest(double hours) { if (mState == State_Loaded) { for (CellRefList::List::iterator it(get().mList.begin()); it != get().mList.end(); ++it) { Ptr ptr = getCurrentPtr(&*it); if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0) { MWBase::Environment::get().getMechanicsManager()->restoreDynamicStats(ptr, hours, true); } } for (CellRefList::List::iterator it(get().mList.begin()); it != get().mList.end(); ++it) { Ptr ptr = getCurrentPtr(&*it); if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0) { MWBase::Environment::get().getMechanicsManager()->restoreDynamicStats(ptr, hours, true); } } } } void CellStore::recharge(float duration) { if (duration <= 0) return; if (mState == State_Loaded) { for (CellRefList::List::iterator it(get().mList.begin()); it != get().mList.end(); ++it) { Ptr ptr = getCurrentPtr(&*it); if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0) { ptr.getClass().getContainerStore(ptr).rechargeItems(duration); } } for (CellRefList::List::iterator it(get().mList.begin()); it != get().mList.end(); ++it) { Ptr ptr = getCurrentPtr(&*it); if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0) { ptr.getClass().getContainerStore(ptr).rechargeItems(duration); } } for (CellRefList::List::iterator it(get().mList.begin()); it != get().mList.end(); ++it) { Ptr ptr = getCurrentPtr(&*it); if (!ptr.isEmpty() && ptr.getRefData().getCustomData() != nullptr && ptr.getRefData().getCount() > 0 && ptr.getClass().getContainerStore(ptr).isResolved()) { ptr.getClass().getContainerStore(ptr).rechargeItems(duration); } } rechargeItems(duration); } } void CellStore::respawn() { if (mState == State_Loaded) { static const int iMonthsToRespawn = mStore.get().find("iMonthsToRespawn")->mValue.getInteger(); if (MWBase::Environment::get().getWorld()->getTimeStamp() - mLastRespawn > 24 * 30 * iMonthsToRespawn) { mLastRespawn = MWBase::Environment::get().getWorld()->getTimeStamp(); for (CellRefList::List::iterator it(get().mList.begin()); it != get().mList.end(); ++it) { Ptr ptr = getCurrentPtr(&*it); ptr.getClass().respawn(ptr); } } for (CellRefList::List::iterator it(get().mList.begin()); it != get().mList.end(); ++it) { Ptr ptr = getCurrentPtr(&*it); clearCorpse(ptr, mStore); ptr.getClass().respawn(ptr); } for (CellRefList::List::iterator it(get().mList.begin()); it != get().mList.end(); ++it) { Ptr ptr = getCurrentPtr(&*it); clearCorpse(ptr, mStore); ptr.getClass().respawn(ptr); } for (CellRefList::List::iterator it(get().mList.begin()); it != get().mList.end(); ++it) { Ptr ptr = getCurrentPtr(&*it); // no need to clearCorpse, handled as part of get() ptr.getClass().respawn(ptr); } } } void MWWorld::CellStore::rechargeItems(float duration) { if (!mRechargingItemsUpToDate) { updateRechargingItems(); mRechargingItemsUpToDate = true; } for (const auto& [item, charge] : mRechargingItems) { MWMechanics::rechargeItem(item, charge, duration); } } void MWWorld::CellStore::updateRechargingItems() { mRechargingItems.clear(); const auto update = [this](auto& list) { for (auto& item : list) { Ptr ptr = getCurrentPtr(&item); if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0) { checkItem(ptr); } } }; update(get().mList); update(get().mList); update(get().mList); update(get().mList); } void MWWorld::CellStore::checkItem(const Ptr& ptr) { const ESM::RefId& enchantmentId = ptr.getClass().getEnchantment(ptr); if (enchantmentId.empty()) return; const ESM::Enchantment* enchantment = mStore.get().search(enchantmentId); if (!enchantment) { Log(Debug::Warning) << "Warning: Can't find enchantment '" << enchantmentId << "' on item " << ptr.getCellRef().getRefId(); return; } if (enchantment->mData.mType == ESM::Enchantment::WhenUsed || enchantment->mData.mType == ESM::Enchantment::WhenStrikes) mRechargingItems.emplace_back(ptr.getBase(), static_cast(enchantment->mData.mCharge)); } Ptr MWWorld::CellStore::getMovedActor(int actorId) const { for (const auto& [cellRef, cell] : mMovedToAnotherCell) { if (cellRef->mClass->isActor() && cellRef->mData.getCustomData()) { Ptr actor(cellRef, cell); if (actor.getClass().getCreatureStats(actor).getActorId() == actorId) return actor; } } return {}; } Ptr CellStore::getPtr(ESM::RefId id) { if (mState == CellStore::State_Unloaded) preload(); if (mState == CellStore::State_Preloaded) { if (!std::binary_search(mIds.begin(), mIds.end(), id)) return Ptr(); load(); } Ptr ptr = search(id); if (!ptr.isEmpty() && isAccessible(ptr.getRefData(), ptr.getCellRef())) return ptr; return Ptr(); } }