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