1 #include "cellstore.hpp"
2 
3 #include <algorithm>
4 
5 #include <components/debug/debuglog.hpp>
6 
7 #include <components/esm/cellstate.hpp>
8 #include <components/esm/cellid.hpp>
9 #include <components/esm/cellref.hpp>
10 #include <components/esm/esmreader.hpp>
11 #include <components/esm/esmwriter.hpp>
12 #include <components/esm/objectstate.hpp>
13 #include <components/esm/containerstate.hpp>
14 #include <components/esm/npcstate.hpp>
15 #include <components/esm/creaturestate.hpp>
16 #include <components/esm/fogstate.hpp>
17 #include <components/esm/creaturelevliststate.hpp>
18 #include <components/esm/doorstate.hpp>
19 
20 #include "../mwbase/environment.hpp"
21 #include "../mwbase/mechanicsmanager.hpp"
22 #include "../mwbase/world.hpp"
23 
24 #include "../mwmechanics/creaturestats.hpp"
25 #include "../mwmechanics/recharge.hpp"
26 
27 #include "ptr.hpp"
28 #include "esmstore.hpp"
29 #include "class.hpp"
30 #include "containerstore.hpp"
31 
32 namespace
33 {
34     template<typename T>
searchInContainerList(MWWorld::CellRefList<T> & containerList,const std::string & id)35     MWWorld::Ptr searchInContainerList (MWWorld::CellRefList<T>& containerList, const std::string& id)
36     {
37         for (typename MWWorld::CellRefList<T>::List::iterator iter (containerList.mList.begin());
38              iter!=containerList.mList.end(); ++iter)
39         {
40             MWWorld::Ptr container (&*iter, nullptr);
41 
42             if (container.getRefData().getCustomData() == nullptr)
43                 continue;
44 
45             MWWorld::Ptr ptr =
46                 container.getClass().getContainerStore (container).search (id);
47 
48             if (!ptr.isEmpty())
49                 return ptr;
50         }
51 
52         return MWWorld::Ptr();
53     }
54 
55     template<typename T>
searchViaActorId(MWWorld::CellRefList<T> & actorList,int actorId,MWWorld::CellStore * cell,const std::map<MWWorld::LiveCellRefBase *,MWWorld::CellStore * > & toIgnore)56     MWWorld::Ptr searchViaActorId (MWWorld::CellRefList<T>& actorList, int actorId,
57         MWWorld::CellStore *cell, const std::map<MWWorld::LiveCellRefBase*, MWWorld::CellStore*>& toIgnore)
58     {
59         for (typename MWWorld::CellRefList<T>::List::iterator iter (actorList.mList.begin());
60              iter!=actorList.mList.end(); ++iter)
61         {
62             MWWorld::Ptr actor (&*iter, cell);
63 
64             if (toIgnore.find(&*iter) != toIgnore.end())
65                 continue;
66 
67             if (actor.getClass().getCreatureStats (actor).matchesActorId (actorId) && actor.getRefData().getCount() > 0)
68                 return actor;
69         }
70 
71         return MWWorld::Ptr();
72     }
73 
74     template<typename RecordType, typename T>
writeReferenceCollection(ESM::ESMWriter & writer,const MWWorld::CellRefList<T> & collection)75     void writeReferenceCollection (ESM::ESMWriter& writer,
76         const MWWorld::CellRefList<T>& collection)
77     {
78         if (!collection.mList.empty())
79         {
80             // references
81             for (typename MWWorld::CellRefList<T>::List::const_iterator
82                 iter (collection.mList.begin());
83                 iter!=collection.mList.end(); ++iter)
84             {
85                 if (!iter->mData.hasChanged() && !iter->mRef.hasChanged() && iter->mRef.hasContentFile())
86                 {
87                     // Reference that came from a content file and has not been changed -> ignore
88                     continue;
89                 }
90                 if (iter->mData.getCount()==0 && !iter->mRef.hasContentFile())
91                 {
92                     // Deleted reference that did not come from a content file -> ignore
93                     continue;
94                 }
95 
96                 RecordType state;
97                 iter->save (state);
98 
99                 // recordId currently unused
100                 writer.writeHNT ("OBJE", collection.mList.front().mBase->sRecordId);
101 
102                 state.save (writer);
103             }
104         }
105     }
106 
107     template<class RecordType, class T>
fixRestockingImpl(const T * base,RecordType & state)108     void fixRestockingImpl(const T* base, RecordType& state)
109     {
110         // Workaround for old saves not containing negative quantities
111         for(const auto& baseItem : base->mInventory.mList)
112         {
113             if(baseItem.mCount < 0)
114             {
115                 for(auto& item : state.mInventory.mItems)
116                 {
117                     if(item.mCount > 0 && Misc::StringUtils::ciEqual(baseItem.mItem, item.mRef.mRefID))
118                         item.mCount = -item.mCount;
119                 }
120             }
121         }
122     }
123 
124     template<class RecordType, class T>
fixRestocking(const T * base,RecordType & state)125     void fixRestocking(const T* base, RecordType& state)
126     {}
127 
128     template<>
fixRestocking(const ESM::Creature * base,ESM::CreatureState & state)129     void fixRestocking<>(const ESM::Creature* base, ESM::CreatureState& state)
130     {
131         fixRestockingImpl(base, state);
132     }
133 
134     template<>
fixRestocking(const ESM::NPC * base,ESM::NpcState & state)135     void fixRestocking<>(const ESM::NPC* base, ESM::NpcState& state)
136     {
137         fixRestockingImpl(base, state);
138     }
139 
140     template<>
fixRestocking(const ESM::Container * base,ESM::ContainerState & state)141     void fixRestocking<>(const ESM::Container* base, ESM::ContainerState& state)
142     {
143         fixRestockingImpl(base, state);
144     }
145 
146     template<typename RecordType, typename T>
readReferenceCollection(ESM::ESMReader & reader,MWWorld::CellRefList<T> & collection,const ESM::CellRef & cref,const std::map<int,int> & contentFileMap,MWWorld::CellStore * cellstore)147     void readReferenceCollection (ESM::ESMReader& reader,
148         MWWorld::CellRefList<T>& collection, const ESM::CellRef& cref, const std::map<int, int>& contentFileMap, MWWorld::CellStore* cellstore)
149     {
150         const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore();
151 
152         RecordType state;
153         state.mRef = cref;
154         state.load(reader);
155 
156         // If the reference came from a content file, make sure this content file is loaded
157         if (state.mRef.mRefNum.hasContentFile())
158         {
159             std::map<int, int>::const_iterator iter =
160                 contentFileMap.find (state.mRef.mRefNum.mContentFile);
161 
162             if (iter==contentFileMap.end())
163                 return; // content file has been removed -> skip
164 
165             state.mRef.mRefNum.mContentFile = iter->second;
166         }
167 
168         if (!MWWorld::LiveCellRef<T>::checkState (state))
169             return; // not valid anymore with current content files -> skip
170 
171         const T *record = esmStore.get<T>().search (state.mRef.mRefID);
172 
173         if (!record)
174             return;
175 
176         if (state.mVersion < 15)
177             fixRestocking(record, state);
178 
179         if (state.mRef.mRefNum.hasContentFile())
180         {
181             for (typename MWWorld::CellRefList<T>::List::iterator iter (collection.mList.begin());
182                 iter!=collection.mList.end(); ++iter)
183                 if (iter->mRef.getRefNum()==state.mRef.mRefNum && *iter->mRef.getRefIdPtr() == state.mRef.mRefID)
184                 {
185                     // overwrite existing reference
186                     float oldscale = iter->mRef.getScale();
187                     iter->load (state);
188                     const ESM::Position & oldpos = iter->mRef.getPosition();
189                     const ESM::Position & newpos = iter->mData.getPosition();
190                     const MWWorld::Ptr ptr(&*iter, cellstore);
191                     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())
192                         MWBase::Environment::get().getWorld()->moveObject(ptr, newpos.pos[0], newpos.pos[1], newpos.pos[2]);
193                     if (!iter->mData.isEnabled())
194                     {
195                         iter->mData.enable();
196                         MWBase::Environment::get().getWorld()->disable(MWWorld::Ptr(&*iter, cellstore));
197                     }
198                     return;
199                 }
200 
201             Log(Debug::Warning) << "Warning: Dropping reference to " << state.mRef.mRefID << " (invalid content file link)";
202             return;
203         }
204 
205         // new reference
206         MWWorld::LiveCellRef<T> ref (record);
207         ref.load (state);
208         collection.mList.push_back (ref);
209     }
210 }
211 
212 namespace MWWorld
213 {
214 
215     template <typename X>
load(ESM::CellRef & ref,bool deleted,const MWWorld::ESMStore & esmStore)216     void CellRefList<X>::load(ESM::CellRef &ref, bool deleted, const MWWorld::ESMStore &esmStore)
217     {
218         const MWWorld::Store<X> &store = esmStore.get<X>();
219 
220         if (const X *ptr = store.search (ref.mRefID))
221         {
222             typename std::list<LiveRef>::iterator iter =
223                 std::find(mList.begin(), mList.end(), ref.mRefNum);
224 
225             LiveRef liveCellRef (ref, ptr);
226 
227             if (deleted)
228                 liveCellRef.mData.setDeletedByContentFile(true);
229 
230             if (iter != mList.end())
231                 *iter = liveCellRef;
232             else
233                 mList.push_back (liveCellRef);
234         }
235         else
236         {
237             Log(Debug::Warning)
238                 << "Warning: could not resolve cell reference '" << ref.mRefID << "'"
239                 << " (dropping reference)";
240         }
241     }
242 
operator ==(const LiveCellRef<X> & ref,int pRefnum)243     template<typename X> bool operator==(const LiveCellRef<X>& ref, int pRefnum)
244     {
245         return (ref.mRef.mRefnum == pRefnum);
246     }
247 
getCurrentPtr(LiveCellRefBase * ref)248     Ptr CellStore::getCurrentPtr(LiveCellRefBase *ref)
249     {
250         MovedRefTracker::iterator found = mMovedToAnotherCell.find(ref);
251         if (found != mMovedToAnotherCell.end())
252             return Ptr(ref, found->second);
253         return Ptr(ref, this);
254     }
255 
moveFrom(const Ptr & object,CellStore * from)256     void CellStore::moveFrom(const Ptr &object, CellStore *from)
257     {
258         if (mState != State_Loaded)
259             load();
260 
261         mHasState = true;
262         MovedRefTracker::iterator found = mMovedToAnotherCell.find(object.getBase());
263         if (found != mMovedToAnotherCell.end())
264         {
265             // A cell we had previously moved an object to is returning it to us.
266             assert (found->second == from);
267             mMovedToAnotherCell.erase(found);
268         }
269         else
270         {
271             mMovedHere.insert(std::make_pair(object.getBase(), from));
272         }
273         updateMergedRefs();
274     }
275 
moveTo(const Ptr & object,CellStore * cellToMoveTo)276     MWWorld::Ptr CellStore::moveTo(const Ptr &object, CellStore *cellToMoveTo)
277     {
278         if (cellToMoveTo == this)
279             throw std::runtime_error("moveTo: object is already in this cell");
280 
281         // We assume that *this is in State_Loaded since we could hardly have reference to a live object otherwise.
282         if (mState != State_Loaded)
283             throw std::runtime_error("moveTo: can't move object from a non-loaded cell (how did you get this object anyway?)");
284 
285         // Ensure that the object actually exists in the cell
286         if (searchViaRefNum(object.getCellRef().getRefNum()).isEmpty())
287             throw std::runtime_error("moveTo: object is not in this cell");
288 
289 
290         // Objects with no refnum can't be handled correctly in the merging process that happens
291         // on a save/load, so do a simple copy & delete for these objects.
292         if (!object.getCellRef().getRefNum().hasContentFile())
293         {
294             MWWorld::Ptr copied = object.getClass().copyToCell(object, *cellToMoveTo, object.getRefData().getCount());
295             object.getRefData().setCount(0);
296             object.getRefData().setBaseNode(nullptr);
297             return copied;
298         }
299 
300         MovedRefTracker::iterator found = mMovedHere.find(object.getBase());
301         if (found != mMovedHere.end())
302         {
303             // Special case - object didn't originate in this cell
304             // Move it back to its original cell first
305             CellStore* originalCell = found->second;
306             assert (originalCell != this);
307             originalCell->moveFrom(object, this);
308 
309             mMovedHere.erase(found);
310 
311             // Now that object is back to its rightful owner, we can move it
312             if (cellToMoveTo != originalCell)
313             {
314                 originalCell->moveTo(object, cellToMoveTo);
315             }
316 
317             updateMergedRefs();
318             return MWWorld::Ptr(object.getBase(), cellToMoveTo);
319         }
320 
321         cellToMoveTo->moveFrom(object, this);
322         mMovedToAnotherCell.insert(std::make_pair(object.getBase(), cellToMoveTo));
323 
324         updateMergedRefs();
325         return MWWorld::Ptr(object.getBase(), cellToMoveTo);
326     }
327 
328     struct MergeVisitor
329     {
MergeVisitorMWWorld::MergeVisitor330         MergeVisitor(std::vector<LiveCellRefBase*>& mergeTo, const std::map<LiveCellRefBase*, MWWorld::CellStore*>& movedHere,
331                      const std::map<LiveCellRefBase*, MWWorld::CellStore*>& movedToAnotherCell)
332             : mMergeTo(mergeTo)
333             , mMovedHere(movedHere)
334             , mMovedToAnotherCell(movedToAnotherCell)
335         {
336         }
337 
operator ()MWWorld::MergeVisitor338         bool operator() (const MWWorld::Ptr& ptr)
339         {
340             if (mMovedToAnotherCell.find(ptr.getBase()) != mMovedToAnotherCell.end())
341                 return true;
342             mMergeTo.push_back(ptr.getBase());
343             return true;
344         }
345 
mergeMWWorld::MergeVisitor346         void merge()
347         {
348             for (const auto & [base, _] : mMovedHere)
349                 mMergeTo.push_back(base);
350         }
351 
352     private:
353         std::vector<LiveCellRefBase*>& mMergeTo;
354 
355         const std::map<LiveCellRefBase*, MWWorld::CellStore*>& mMovedHere;
356         const std::map<LiveCellRefBase*, MWWorld::CellStore*>& mMovedToAnotherCell;
357     };
358 
updateMergedRefs()359     void CellStore::updateMergedRefs()
360     {
361         mMergedRefs.clear();
362         mRechargingItemsUpToDate = false;
363         MergeVisitor visitor(mMergedRefs, mMovedHere, mMovedToAnotherCell);
364         forEachInternal(visitor);
365         visitor.merge();
366     }
367 
movedHere(const MWWorld::Ptr & ptr) const368     bool CellStore::movedHere(const MWWorld::Ptr& ptr) const
369     {
370         if (ptr.isEmpty())
371             return false;
372 
373         if (mMovedHere.find(ptr.getBase()) != mMovedHere.end())
374             return true;
375 
376         return false;
377     }
378 
CellStore(const ESM::Cell * cell,const MWWorld::ESMStore & esmStore,std::vector<ESM::ESMReader> & readerList)379     CellStore::CellStore (const ESM::Cell *cell, const MWWorld::ESMStore& esmStore, std::vector<ESM::ESMReader>& readerList)
380         : mStore(esmStore), mReader(readerList), mCell (cell), mState (State_Unloaded), mHasState (false), mLastRespawn(0,0), mRechargingItemsUpToDate(false)
381     {
382         mWaterLevel = cell->mWater;
383     }
384 
getCell() const385     const ESM::Cell *CellStore::getCell() const
386     {
387         return mCell;
388     }
389 
getState() const390     CellStore::State CellStore::getState() const
391     {
392         return mState;
393     }
394 
getPreloadedIds() const395     const std::vector<std::string> &CellStore::getPreloadedIds() const
396     {
397         return mIds;
398     }
399 
hasState() const400     bool CellStore::hasState() const
401     {
402         return mHasState;
403     }
404 
hasId(const std::string & id) const405     bool CellStore::hasId (const std::string& id) const
406     {
407         if (mState==State_Unloaded)
408             return false;
409 
410         if (mState==State_Preloaded)
411             return std::binary_search (mIds.begin(), mIds.end(), id);
412 
413         return searchConst (id).isEmpty();
414     }
415 
416     template <typename PtrType>
417     struct SearchVisitor
418     {
419         PtrType mFound;
420         const std::string *mIdToFind;
operator ()MWWorld::SearchVisitor421         bool operator()(const PtrType& ptr)
422         {
423             if (*ptr.getCellRef().getRefIdPtr() == *mIdToFind)
424             {
425                 mFound = ptr;
426                 return false;
427             }
428             return true;
429         }
430     };
431 
search(const std::string & id)432     Ptr CellStore::search (const std::string& id)
433     {
434         SearchVisitor<MWWorld::Ptr> searchVisitor;
435         searchVisitor.mIdToFind = &id;
436         forEach(searchVisitor);
437         return searchVisitor.mFound;
438     }
439 
searchConst(const std::string & id) const440     ConstPtr CellStore::searchConst (const std::string& id) const
441     {
442         SearchVisitor<MWWorld::ConstPtr> searchVisitor;
443         searchVisitor.mIdToFind = &id;
444         forEachConst(searchVisitor);
445         return searchVisitor.mFound;
446     }
447 
searchViaActorId(int id)448     Ptr CellStore::searchViaActorId (int id)
449     {
450         if (Ptr ptr = ::searchViaActorId (mNpcs, id, this, mMovedToAnotherCell))
451             return ptr;
452 
453         if (Ptr ptr = ::searchViaActorId (mCreatures, id, this, mMovedToAnotherCell))
454             return ptr;
455 
456         for (const auto& [base, _] : mMovedHere)
457         {
458             MWWorld::Ptr actor (base, this);
459             if (!actor.getClass().isActor())
460                 continue;
461             if (actor.getClass().getCreatureStats (actor).matchesActorId (id) && actor.getRefData().getCount() > 0)
462                 return actor;
463         }
464 
465         return Ptr();
466     }
467 
468     class RefNumSearchVisitor
469     {
470         const ESM::RefNum& mRefNum;
471     public:
RefNumSearchVisitor(const ESM::RefNum & refNum)472         RefNumSearchVisitor(const ESM::RefNum& refNum) : mRefNum(refNum) {}
473 
474         Ptr mFound;
475 
operator ()(const Ptr & ptr)476         bool operator()(const Ptr& ptr)
477         {
478             if (ptr.getCellRef().getRefNum() == mRefNum)
479             {
480                 mFound = ptr;
481                 return false;
482             }
483             return true;
484         }
485     };
486 
searchViaRefNum(const ESM::RefNum & refNum)487     Ptr CellStore::searchViaRefNum (const ESM::RefNum& refNum)
488     {
489         RefNumSearchVisitor searchVisitor(refNum);
490         forEach(searchVisitor);
491         return searchVisitor.mFound;
492     }
493 
getWaterLevel() const494     float CellStore::getWaterLevel() const
495     {
496         if (isExterior())
497             return -1;
498         return mWaterLevel;
499     }
500 
setWaterLevel(float level)501     void CellStore::setWaterLevel (float level)
502     {
503         mWaterLevel = level;
504         mHasState = true;
505     }
506 
count() const507     std::size_t CellStore::count() const
508     {
509         return mMergedRefs.size();
510     }
511 
load()512     void CellStore::load ()
513     {
514         if (mState!=State_Loaded)
515         {
516             if (mState==State_Preloaded)
517                 mIds.clear();
518 
519             loadRefs ();
520 
521             mState = State_Loaded;
522         }
523     }
524 
preload()525     void CellStore::preload ()
526     {
527         if (mState==State_Unloaded)
528         {
529             listRefs ();
530 
531             mState = State_Preloaded;
532         }
533     }
534 
listRefs()535     void CellStore::listRefs()
536     {
537         std::vector<ESM::ESMReader>& esm = mReader;
538 
539         assert (mCell);
540 
541         if (mCell->mContextList.empty())
542             return; // this is a dynamically generated cell -> skipping.
543 
544         // Load references from all plugins that do something with this cell.
545         for (size_t i = 0; i < mCell->mContextList.size(); i++)
546         {
547             try
548             {
549                 // Reopen the ESM reader and seek to the right position.
550                 int index = mCell->mContextList[i].index;
551                 mCell->restore (esm[index], i);
552 
553                 ESM::CellRef ref;
554 
555                 // Get each reference in turn
556                 bool deleted = false;
557                 while (mCell->getNextRef (esm[index], ref, deleted))
558                 {
559                     if (deleted)
560                         continue;
561 
562                     // Don't list reference if it was moved to a different cell.
563                     ESM::MovedCellRefTracker::const_iterator iter =
564                         std::find(mCell->mMovedRefs.begin(), mCell->mMovedRefs.end(), ref.mRefNum);
565                     if (iter != mCell->mMovedRefs.end()) {
566                         continue;
567                     }
568 
569                     Misc::StringUtils::lowerCaseInPlace(ref.mRefID);
570                     mIds.push_back(std::move(ref.mRefID));
571                 }
572             }
573             catch (std::exception& e)
574             {
575                 Log(Debug::Error) << "An error occurred listing references for cell " << getCell()->getDescription() << ": " << e.what();
576             }
577         }
578 
579         // List moved references, from separately tracked list.
580         for (const auto& [ref, deleted]: mCell->mLeasedRefs)
581         {
582             if (!deleted)
583                 mIds.push_back(Misc::StringUtils::lowerCase(ref.mRefID));
584         }
585 
586         std::sort (mIds.begin(), mIds.end());
587     }
588 
loadRefs()589     void CellStore::loadRefs()
590     {
591         std::vector<ESM::ESMReader>& esm = mReader;
592 
593         assert (mCell);
594 
595         if (mCell->mContextList.empty())
596             return; // this is a dynamically generated cell -> skipping.
597 
598         std::map<ESM::RefNum, std::string> refNumToID; // used to detect refID modifications
599 
600         // Load references from all plugins that do something with this cell.
601         for (size_t i = 0; i < mCell->mContextList.size(); i++)
602         {
603             try
604             {
605                 // Reopen the ESM reader and seek to the right position.
606                 int index = mCell->mContextList[i].index;
607                 mCell->restore (esm[index], i);
608 
609                 ESM::CellRef ref;
610                 ref.mRefNum.mContentFile = ESM::RefNum::RefNum_NoContentFile;
611 
612                 // Get each reference in turn
613                 bool deleted = false;
614                 while(mCell->getNextRef(esm[index], ref, deleted))
615                 {
616                     // Don't load reference if it was moved to a different cell.
617                     ESM::MovedCellRefTracker::const_iterator iter =
618                         std::find(mCell->mMovedRefs.begin(), mCell->mMovedRefs.end(), ref.mRefNum);
619                     if (iter != mCell->mMovedRefs.end()) {
620                         continue;
621                     }
622 
623                     loadRef (ref, deleted, refNumToID);
624                 }
625             }
626             catch (std::exception& e)
627             {
628                 Log(Debug::Error) << "An error occurred loading references for cell " << getCell()->getDescription() << ": " << e.what();
629             }
630         }
631 
632         // Load moved references, from separately tracked list.
633         for (const auto& leasedRef : mCell->mLeasedRefs)
634         {
635             ESM::CellRef &ref = const_cast<ESM::CellRef&>(leasedRef.first);
636             bool deleted = leasedRef.second;
637 
638             loadRef (ref, deleted, refNumToID);
639         }
640 
641         updateMergedRefs();
642     }
643 
isExterior() const644     bool CellStore::isExterior() const
645     {
646         return mCell->isExterior();
647     }
648 
searchInContainer(const std::string & id)649     Ptr CellStore::searchInContainer (const std::string& id)
650     {
651         bool oldState = mHasState;
652 
653         mHasState = true;
654 
655         if (Ptr ptr = searchInContainerList (mContainers, id))
656             return ptr;
657 
658         if (Ptr ptr = searchInContainerList (mCreatures, id))
659             return ptr;
660 
661         if (Ptr ptr = searchInContainerList (mNpcs, id))
662             return ptr;
663 
664         mHasState = oldState;
665 
666         return Ptr();
667     }
668 
loadRef(ESM::CellRef & ref,bool deleted,std::map<ESM::RefNum,std::string> & refNumToID)669     void CellStore::loadRef (ESM::CellRef& ref, bool deleted, std::map<ESM::RefNum, std::string>& refNumToID)
670     {
671         Misc::StringUtils::lowerCaseInPlace (ref.mRefID);
672 
673         const MWWorld::ESMStore& store = mStore;
674 
675         std::map<ESM::RefNum, std::string>::iterator it = refNumToID.find(ref.mRefNum);
676         if (it != refNumToID.end())
677         {
678             if (it->second != ref.mRefID)
679             {
680                 // refID was modified, make sure we don't end up with duplicated refs
681                 switch (store.find(it->second))
682                 {
683                     case ESM::REC_ACTI: mActivators.remove(ref.mRefNum); break;
684                     case ESM::REC_ALCH: mPotions.remove(ref.mRefNum); break;
685                     case ESM::REC_APPA: mAppas.remove(ref.mRefNum); break;
686                     case ESM::REC_ARMO: mArmors.remove(ref.mRefNum); break;
687                     case ESM::REC_BOOK: mBooks.remove(ref.mRefNum); break;
688                     case ESM::REC_CLOT: mClothes.remove(ref.mRefNum); break;
689                     case ESM::REC_CONT: mContainers.remove(ref.mRefNum); break;
690                     case ESM::REC_CREA: mCreatures.remove(ref.mRefNum); break;
691                     case ESM::REC_DOOR: mDoors.remove(ref.mRefNum); break;
692                     case ESM::REC_INGR: mIngreds.remove(ref.mRefNum); break;
693                     case ESM::REC_LEVC: mCreatureLists.remove(ref.mRefNum); break;
694                     case ESM::REC_LEVI: mItemLists.remove(ref.mRefNum); break;
695                     case ESM::REC_LIGH: mLights.remove(ref.mRefNum); break;
696                     case ESM::REC_LOCK: mLockpicks.remove(ref.mRefNum); break;
697                     case ESM::REC_MISC: mMiscItems.remove(ref.mRefNum); break;
698                     case ESM::REC_NPC_: mNpcs.remove(ref.mRefNum); break;
699                     case ESM::REC_PROB: mProbes.remove(ref.mRefNum); break;
700                     case ESM::REC_REPA: mRepairs.remove(ref.mRefNum); break;
701                     case ESM::REC_STAT: mStatics.remove(ref.mRefNum); break;
702                     case ESM::REC_WEAP: mWeapons.remove(ref.mRefNum); break;
703                     case ESM::REC_BODY: mBodyParts.remove(ref.mRefNum); break;
704                     default:
705                         break;
706                 }
707             }
708         }
709 
710         switch (store.find (ref.mRefID))
711         {
712             case ESM::REC_ACTI: mActivators.load(ref, deleted, store); break;
713             case ESM::REC_ALCH: mPotions.load(ref, deleted,store); break;
714             case ESM::REC_APPA: mAppas.load(ref, deleted, store); break;
715             case ESM::REC_ARMO: mArmors.load(ref, deleted, store); break;
716             case ESM::REC_BOOK: mBooks.load(ref, deleted, store); break;
717             case ESM::REC_CLOT: mClothes.load(ref, deleted, store); break;
718             case ESM::REC_CONT: mContainers.load(ref, deleted, store); break;
719             case ESM::REC_CREA: mCreatures.load(ref, deleted, store); break;
720             case ESM::REC_DOOR: mDoors.load(ref, deleted, store); break;
721             case ESM::REC_INGR: mIngreds.load(ref, deleted, store); break;
722             case ESM::REC_LEVC: mCreatureLists.load(ref, deleted, store); break;
723             case ESM::REC_LEVI: mItemLists.load(ref, deleted, store); break;
724             case ESM::REC_LIGH: mLights.load(ref, deleted, store); break;
725             case ESM::REC_LOCK: mLockpicks.load(ref, deleted, store); break;
726             case ESM::REC_MISC: mMiscItems.load(ref, deleted, store); break;
727             case ESM::REC_NPC_: mNpcs.load(ref, deleted, store); break;
728             case ESM::REC_PROB: mProbes.load(ref, deleted, store); break;
729             case ESM::REC_REPA: mRepairs.load(ref, deleted, store); break;
730             case ESM::REC_STAT:
731             {
732                 if (ref.mRefNum.fromGroundcoverFile()) return;
733                 mStatics.load(ref, deleted, store); break;
734             }
735             case ESM::REC_WEAP: mWeapons.load(ref, deleted, store); break;
736             case ESM::REC_BODY: mBodyParts.load(ref, deleted, store); break;
737 
738             case 0: Log(Debug::Error) << "Cell reference '" + ref.mRefID + "' not found!"; return;
739 
740             default:
741                 Log(Debug::Error) << "Error: Ignoring reference '" << ref.mRefID << "' of unhandled type";
742                 return;
743         }
744 
745         refNumToID[ref.mRefNum] = ref.mRefID;
746     }
747 
loadState(const ESM::CellState & state)748     void CellStore::loadState (const ESM::CellState& state)
749     {
750         mHasState = true;
751 
752         if (mCell->mData.mFlags & ESM::Cell::Interior && mCell->mData.mFlags & ESM::Cell::HasWater)
753             mWaterLevel = state.mWaterLevel;
754 
755         mLastRespawn = MWWorld::TimeStamp(state.mLastRespawn);
756     }
757 
saveState(ESM::CellState & state) const758     void CellStore::saveState (ESM::CellState& state) const
759     {
760         state.mId = mCell->getCellId();
761 
762         if (mCell->mData.mFlags & ESM::Cell::Interior && mCell->mData.mFlags & ESM::Cell::HasWater)
763             state.mWaterLevel = mWaterLevel;
764 
765         state.mHasFogOfWar = (mFogState.get() ? 1 : 0);
766         state.mLastRespawn = mLastRespawn.toEsm();
767     }
768 
writeFog(ESM::ESMWriter & writer) const769     void CellStore::writeFog(ESM::ESMWriter &writer) const
770     {
771         if (mFogState.get())
772         {
773             mFogState->save(writer, mCell->mData.mFlags & ESM::Cell::Interior);
774         }
775     }
776 
readFog(ESM::ESMReader & reader)777     void CellStore::readFog(ESM::ESMReader &reader)
778     {
779         mFogState.reset(new ESM::FogState());
780         mFogState->load(reader);
781     }
782 
writeReferences(ESM::ESMWriter & writer) const783     void CellStore::writeReferences (ESM::ESMWriter& writer) const
784     {
785         writeReferenceCollection<ESM::ObjectState> (writer, mActivators);
786         writeReferenceCollection<ESM::ObjectState> (writer, mPotions);
787         writeReferenceCollection<ESM::ObjectState> (writer, mAppas);
788         writeReferenceCollection<ESM::ObjectState> (writer, mArmors);
789         writeReferenceCollection<ESM::ObjectState> (writer, mBooks);
790         writeReferenceCollection<ESM::ObjectState> (writer, mClothes);
791         writeReferenceCollection<ESM::ContainerState> (writer, mContainers);
792         writeReferenceCollection<ESM::CreatureState> (writer, mCreatures);
793         writeReferenceCollection<ESM::DoorState> (writer, mDoors);
794         writeReferenceCollection<ESM::ObjectState> (writer, mIngreds);
795         writeReferenceCollection<ESM::CreatureLevListState> (writer, mCreatureLists);
796         writeReferenceCollection<ESM::ObjectState> (writer, mItemLists);
797         writeReferenceCollection<ESM::ObjectState> (writer, mLights);
798         writeReferenceCollection<ESM::ObjectState> (writer, mLockpicks);
799         writeReferenceCollection<ESM::ObjectState> (writer, mMiscItems);
800         writeReferenceCollection<ESM::NpcState> (writer, mNpcs);
801         writeReferenceCollection<ESM::ObjectState> (writer, mProbes);
802         writeReferenceCollection<ESM::ObjectState> (writer, mRepairs);
803         writeReferenceCollection<ESM::ObjectState> (writer, mStatics);
804         writeReferenceCollection<ESM::ObjectState> (writer, mWeapons);
805         writeReferenceCollection<ESM::ObjectState> (writer, mBodyParts);
806 
807         for (const auto& [base, store] : mMovedToAnotherCell)
808         {
809             ESM::RefNum refNum = base->mRef.getRefNum();
810             ESM::CellId movedTo = store->getCell()->getCellId();
811 
812             refNum.save(writer, true, "MVRF");
813             movedTo.save(writer);
814         }
815     }
816 
readReferences(ESM::ESMReader & reader,const std::map<int,int> & contentFileMap,GetCellStoreCallback * callback)817     void CellStore::readReferences (ESM::ESMReader& reader, const std::map<int, int>& contentFileMap, GetCellStoreCallback* callback)
818     {
819         mHasState = true;
820 
821         while (reader.isNextSub ("OBJE"))
822         {
823             unsigned int unused;
824             reader.getHT (unused);
825 
826             // load the RefID first so we know what type of object it is
827             ESM::CellRef cref;
828             cref.loadId(reader, true);
829 
830             int type = MWBase::Environment::get().getWorld()->getStore().find(cref.mRefID);
831             if (type == 0)
832             {
833                 Log(Debug::Warning) << "Dropping reference to '" << cref.mRefID << "' (object no longer exists)";
834                 reader.skipHSubUntil("OBJE");
835                 continue;
836             }
837 
838             switch (type)
839             {
840                 case ESM::REC_ACTI:
841 
842                     readReferenceCollection<ESM::ObjectState> (reader, mActivators, cref, contentFileMap, this);
843                     break;
844 
845                 case ESM::REC_ALCH:
846 
847                     readReferenceCollection<ESM::ObjectState> (reader, mPotions, cref, contentFileMap, this);
848                     break;
849 
850                 case ESM::REC_APPA:
851 
852                     readReferenceCollection<ESM::ObjectState> (reader, mAppas, cref, contentFileMap, this);
853                     break;
854 
855                 case ESM::REC_ARMO:
856 
857                     readReferenceCollection<ESM::ObjectState> (reader, mArmors, cref, contentFileMap, this);
858                     break;
859 
860                 case ESM::REC_BOOK:
861 
862                     readReferenceCollection<ESM::ObjectState> (reader, mBooks, cref, contentFileMap, this);
863                     break;
864 
865                 case ESM::REC_CLOT:
866 
867                     readReferenceCollection<ESM::ObjectState> (reader, mClothes, cref, contentFileMap, this);
868                     break;
869 
870                 case ESM::REC_CONT:
871 
872                     readReferenceCollection<ESM::ContainerState> (reader, mContainers, cref, contentFileMap, this);
873                     break;
874 
875                 case ESM::REC_CREA:
876 
877                     readReferenceCollection<ESM::CreatureState> (reader, mCreatures, cref, contentFileMap, this);
878                     break;
879 
880                 case ESM::REC_DOOR:
881 
882                     readReferenceCollection<ESM::DoorState> (reader, mDoors, cref, contentFileMap, this);
883                     break;
884 
885                 case ESM::REC_INGR:
886 
887                     readReferenceCollection<ESM::ObjectState> (reader, mIngreds, cref, contentFileMap, this);
888                     break;
889 
890                 case ESM::REC_LEVC:
891 
892                     readReferenceCollection<ESM::CreatureLevListState> (reader, mCreatureLists, cref, contentFileMap, this);
893                     break;
894 
895                 case ESM::REC_LEVI:
896 
897                     readReferenceCollection<ESM::ObjectState> (reader, mItemLists, cref, contentFileMap, this);
898                     break;
899 
900                 case ESM::REC_LIGH:
901 
902                     readReferenceCollection<ESM::ObjectState> (reader, mLights, cref, contentFileMap, this);
903                     break;
904 
905                 case ESM::REC_LOCK:
906 
907                     readReferenceCollection<ESM::ObjectState> (reader, mLockpicks, cref, contentFileMap, this);
908                     break;
909 
910                 case ESM::REC_MISC:
911 
912                     readReferenceCollection<ESM::ObjectState> (reader, mMiscItems, cref, contentFileMap, this);
913                     break;
914 
915                 case ESM::REC_NPC_:
916 
917                     readReferenceCollection<ESM::NpcState> (reader, mNpcs, cref, contentFileMap, this);
918                     break;
919 
920                 case ESM::REC_PROB:
921 
922                     readReferenceCollection<ESM::ObjectState> (reader, mProbes, cref, contentFileMap, this);
923                     break;
924 
925                 case ESM::REC_REPA:
926 
927                     readReferenceCollection<ESM::ObjectState> (reader, mRepairs, cref, contentFileMap, this);
928                     break;
929 
930                 case ESM::REC_STAT:
931 
932                     readReferenceCollection<ESM::ObjectState> (reader, mStatics, cref, contentFileMap, this);
933                     break;
934 
935                 case ESM::REC_WEAP:
936 
937                     readReferenceCollection<ESM::ObjectState> (reader, mWeapons, cref, contentFileMap, this);
938                     break;
939 
940                 case ESM::REC_BODY:
941 
942                     readReferenceCollection<ESM::ObjectState> (reader, mBodyParts, cref, contentFileMap, this);
943                     break;
944 
945                 default:
946 
947                     throw std::runtime_error ("unknown type in cell reference section");
948             }
949         }
950 
951         // Do another update here to make sure objects referred to by MVRF tags can be found
952         // This update is only needed for old saves that used the old copy&delete way of moving objects
953         updateMergedRefs();
954 
955         while (reader.isNextSub("MVRF"))
956         {
957             reader.cacheSubName();
958             ESM::RefNum refnum;
959             ESM::CellId movedTo;
960             refnum.load(reader, true, "MVRF");
961             movedTo.load(reader);
962 
963             if (refnum.hasContentFile())
964             {
965                 auto iter = contentFileMap.find(refnum.mContentFile);
966                 if (iter != contentFileMap.end())
967                     refnum.mContentFile = iter->second;
968             }
969 
970             // Search for the reference. It might no longer exist if its content file was removed.
971             Ptr movedRef = searchViaRefNum(refnum);
972             if (movedRef.isEmpty())
973             {
974                 Log(Debug::Warning) << "Warning: Dropping moved ref tag for " << refnum.mIndex << " (moved object no longer exists)";
975                 continue;
976             }
977 
978             CellStore* otherCell = callback->getCellStore(movedTo);
979 
980             if (otherCell == nullptr)
981             {
982                 Log(Debug::Warning) << "Warning: Dropping moved ref tag for " << movedRef.getCellRef().getRefId()
983                                     << " (target cell " << movedTo.mWorldspace << " no longer exists). Reference moved back to its original location.";
984                 // Note by dropping tag the object will automatically re-appear in its original cell, though potentially at inapproriate coordinates.
985                 // Restore original coordinates:
986                 movedRef.getRefData().setPosition(movedRef.getCellRef().getPosition());
987                 continue;
988             }
989 
990             if (otherCell == this)
991             {
992                 // Should never happen unless someone's tampering with files.
993                 Log(Debug::Warning) << "Found invalid moved ref, ignoring";
994                 continue;
995             }
996 
997             moveTo(movedRef, otherCell);
998         }
999     }
1000 
operator ==(const CellStore & left,const CellStore & right)1001     bool operator== (const CellStore& left, const CellStore& right)
1002     {
1003         return left.getCell()->getCellId()==right.getCell()->getCellId();
1004     }
1005 
operator !=(const CellStore & left,const CellStore & right)1006     bool operator!= (const CellStore& left, const CellStore& right)
1007     {
1008         return !(left==right);
1009     }
1010 
setFog(ESM::FogState * fog)1011     void CellStore::setFog(ESM::FogState *fog)
1012     {
1013         mFogState.reset(fog);
1014     }
1015 
getFog() const1016     ESM::FogState* CellStore::getFog() const
1017     {
1018         return mFogState.get();
1019     }
1020 
clearCorpse(const MWWorld::Ptr & ptr)1021     void clearCorpse(const MWWorld::Ptr& ptr)
1022     {
1023         const MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr);
1024         static const float fCorpseClearDelay = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fCorpseClearDelay")->mValue.getFloat();
1025         if (creatureStats.isDead() &&
1026             creatureStats.isDeathAnimationFinished() &&
1027             !ptr.getClass().isPersistent(ptr) &&
1028             creatureStats.getTimeOfDeath() + fCorpseClearDelay <= MWBase::Environment::get().getWorld()->getTimeStamp())
1029         {
1030             MWBase::Environment::get().getWorld()->deleteObject(ptr);
1031         }
1032     }
1033 
rest(double hours)1034     void CellStore::rest(double hours)
1035     {
1036         if (mState == State_Loaded)
1037         {
1038             for (CellRefList<ESM::Creature>::List::iterator it (mCreatures.mList.begin()); it!=mCreatures.mList.end(); ++it)
1039             {
1040                 Ptr ptr = getCurrentPtr(&*it);
1041                 if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0)
1042                 {
1043                     MWBase::Environment::get().getMechanicsManager()->restoreDynamicStats(ptr, hours, true);
1044                 }
1045             }
1046             for (CellRefList<ESM::NPC>::List::iterator it (mNpcs.mList.begin()); it!=mNpcs.mList.end(); ++it)
1047             {
1048                 Ptr ptr = getCurrentPtr(&*it);
1049                 if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0)
1050                 {
1051                     MWBase::Environment::get().getMechanicsManager()->restoreDynamicStats(ptr, hours, true);
1052                 }
1053             }
1054         }
1055     }
1056 
recharge(float duration)1057     void CellStore::recharge(float duration)
1058     {
1059         if (duration <= 0)
1060             return;
1061 
1062         if (mState == State_Loaded)
1063         {
1064             for (CellRefList<ESM::Creature>::List::iterator it (mCreatures.mList.begin()); it!=mCreatures.mList.end(); ++it)
1065             {
1066                 Ptr ptr = getCurrentPtr(&*it);
1067                 if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0)
1068                 {
1069                     ptr.getClass().getContainerStore(ptr).rechargeItems(duration);
1070                 }
1071             }
1072             for (CellRefList<ESM::NPC>::List::iterator it (mNpcs.mList.begin()); it!=mNpcs.mList.end(); ++it)
1073             {
1074                 Ptr ptr = getCurrentPtr(&*it);
1075                 if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0)
1076                 {
1077                     ptr.getClass().getContainerStore(ptr).rechargeItems(duration);
1078                 }
1079             }
1080             for (CellRefList<ESM::Container>::List::iterator it (mContainers.mList.begin()); it!=mContainers.mList.end(); ++it)
1081             {
1082                 Ptr ptr = getCurrentPtr(&*it);
1083                 if (!ptr.isEmpty() && ptr.getRefData().getCustomData() != nullptr && ptr.getRefData().getCount() > 0
1084                 && ptr.getClass().getContainerStore(ptr).isResolved())
1085                 {
1086                     ptr.getClass().getContainerStore(ptr).rechargeItems(duration);
1087                 }
1088             }
1089 
1090             rechargeItems(duration);
1091         }
1092     }
1093 
respawn()1094     void CellStore::respawn()
1095     {
1096         if (mState == State_Loaded)
1097         {
1098             static const int iMonthsToRespawn = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("iMonthsToRespawn")->mValue.getInteger();
1099             if (MWBase::Environment::get().getWorld()->getTimeStamp() - mLastRespawn > 24*30*iMonthsToRespawn)
1100             {
1101                 mLastRespawn = MWBase::Environment::get().getWorld()->getTimeStamp();
1102                 for (CellRefList<ESM::Container>::List::iterator it (mContainers.mList.begin()); it!=mContainers.mList.end(); ++it)
1103                 {
1104                     Ptr ptr = getCurrentPtr(&*it);
1105                     ptr.getClass().respawn(ptr);
1106                 }
1107             }
1108 
1109             for (CellRefList<ESM::Creature>::List::iterator it (mCreatures.mList.begin()); it!=mCreatures.mList.end(); ++it)
1110             {
1111                 Ptr ptr = getCurrentPtr(&*it);
1112                 clearCorpse(ptr);
1113                 ptr.getClass().respawn(ptr);
1114             }
1115             for (CellRefList<ESM::NPC>::List::iterator it (mNpcs.mList.begin()); it!=mNpcs.mList.end(); ++it)
1116             {
1117                 Ptr ptr = getCurrentPtr(&*it);
1118                 clearCorpse(ptr);
1119                 ptr.getClass().respawn(ptr);
1120             }
1121             for (CellRefList<ESM::CreatureLevList>::List::iterator it (mCreatureLists.mList.begin()); it!=mCreatureLists.mList.end(); ++it)
1122             {
1123                 Ptr ptr = getCurrentPtr(&*it);
1124                 // no need to clearCorpse, handled as part of mCreatures
1125                 ptr.getClass().respawn(ptr);
1126             }
1127         }
1128     }
1129 
rechargeItems(float duration)1130     void MWWorld::CellStore::rechargeItems(float duration)
1131     {
1132         if (!mRechargingItemsUpToDate)
1133         {
1134             updateRechargingItems();
1135             mRechargingItemsUpToDate = true;
1136         }
1137         for (const auto& [item, charge] : mRechargingItems)
1138         {
1139             MWMechanics::rechargeItem(item, charge, duration);
1140         }
1141     }
1142 
updateRechargingItems()1143     void MWWorld::CellStore::updateRechargingItems()
1144     {
1145         mRechargingItems.clear();
1146 
1147         const auto update = [this](auto& list)
1148         {
1149             for (auto & item : list)
1150             {
1151                 Ptr ptr = getCurrentPtr(&item);
1152                 if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0)
1153                 {
1154                     checkItem(ptr);
1155                 }
1156             }
1157         };
1158 
1159         update(mWeapons.mList);
1160         update(mArmors.mList);
1161         update(mClothes.mList);
1162         update(mBooks.mList);
1163     }
1164 
checkItem(Ptr ptr)1165     void MWWorld::CellStore::checkItem(Ptr ptr)
1166     {
1167         if (ptr.getClass().getEnchantment(ptr).empty())
1168             return;
1169 
1170         std::string enchantmentId = ptr.getClass().getEnchantment(ptr);
1171         const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().search(enchantmentId);
1172         if (!enchantment)
1173         {
1174             Log(Debug::Warning) << "Warning: Can't find enchantment '" << enchantmentId << "' on item " << ptr.getCellRef().getRefId();
1175             return;
1176         }
1177 
1178         if (enchantment->mData.mType == ESM::Enchantment::WhenUsed
1179                 || enchantment->mData.mType == ESM::Enchantment::WhenStrikes)
1180             mRechargingItems.emplace_back(ptr.getBase(), static_cast<float>(enchantment->mData.mCharge));
1181     }
1182 }
1183