#include "containerstore.hpp" #include "inventorystore.hpp" #include #include #include #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwmechanics/levelledlist.hpp" #include "../mwmechanics/recharge.hpp" #include "../mwmechanics/spellutil.hpp" #include "class.hpp" #include "esmstore.hpp" #include "localscripts.hpp" #include "manualref.hpp" #include "player.hpp" #include "refdata.hpp" #include "worldmodel.hpp" namespace { void addScripts(MWWorld::ContainerStore& store, MWWorld::CellStore* cell) { auto& scripts = MWBase::Environment::get().getWorld()->getLocalScripts(); for (const auto&& ptr : store) { const auto& script = ptr.getClass().getScript(ptr); if (!script.empty()) { MWWorld::Ptr item = ptr; item.mCell = cell; scripts.add(script, item); } } } template float getTotalWeight(const MWWorld::CellRefList& cellRefList) { float sum = 0; for (const MWWorld::LiveCellRef& liveCellRef : cellRefList.mList) { if (const int count = liveCellRef.mData.getCount(); count > 0) sum += count * liveCellRef.mBase->mData.mWeight; } return sum; } template MWWorld::Ptr searchId(MWWorld::CellRefList& list, const ESM::RefId& id, MWWorld::ContainerStore* store) { store->resolve(); for (MWWorld::LiveCellRef& liveCellRef : list.mList) { if ((liveCellRef.mBase->mId == id) && liveCellRef.mData.getCount()) { MWWorld::Ptr ptr(&liveCellRef, nullptr); ptr.setContainerStore(store); return ptr; } } return MWWorld::Ptr(); } } MWWorld::ResolutionListener::~ResolutionListener() { try { mStore.unresolve(); } catch (const std::exception& e) { Log(Debug::Error) << "Failed to clear temporary container contents: " << e.what(); } } template MWWorld::ContainerStoreIterator MWWorld::ContainerStore::getState( CellRefList& collection, const ESM::ObjectState& state) { if (!LiveCellRef::checkState(state)) return ContainerStoreIterator(this); // not valid anymore with current content files -> skip const T* record = MWBase::Environment::get().getESMStore()->get().search(state.mRef.mRefID); if (!record) return ContainerStoreIterator(this); LiveCellRef ref(record); ref.load(state); collection.mList.push_back(ref); return ContainerStoreIterator(this, --collection.mList.end()); } void MWWorld::ContainerStore::storeEquipmentState( const MWWorld::LiveCellRefBase& ref, int index, ESM::InventoryState& inventory) const { } void MWWorld::ContainerStore::readEquipmentState( const MWWorld::ContainerStoreIterator& iter, int index, const ESM::InventoryState& inventory) { } template void MWWorld::ContainerStore::storeState(const LiveCellRef& ref, ESM::ObjectState& state) const { ref.save(state); } template void MWWorld::ContainerStore::storeStates( const CellRefList& collection, ESM::InventoryState& inventory, int& index, bool equipable) const { for (const LiveCellRef& liveCellRef : collection.mList) { if (liveCellRef.mData.getCount() == 0) continue; ESM::ObjectState state; storeState(liveCellRef, state); if (equipable) storeEquipmentState(liveCellRef, index, inventory); inventory.mItems.push_back(std::move(state)); ++index; } } const ESM::RefId MWWorld::ContainerStore::sGoldId = ESM::RefId::stringRefId("gold_001"); MWWorld::ContainerStore::ContainerStore() : mListener(nullptr) , mRechargingItemsUpToDate(false) , mCachedWeight(0) , mWeightUpToDate(false) , mModified(false) , mResolved(false) , mSeed() , mPtr() { } MWWorld::ContainerStore::~ContainerStore() {} MWWorld::ConstContainerStoreIterator MWWorld::ContainerStore::cbegin(int mask) const { return ConstContainerStoreIterator(mask, this); } MWWorld::ConstContainerStoreIterator MWWorld::ContainerStore::cend() const { return ConstContainerStoreIterator(this); } MWWorld::ConstContainerStoreIterator MWWorld::ContainerStore::begin(int mask) const { return cbegin(mask); } MWWorld::ConstContainerStoreIterator MWWorld::ContainerStore::end() const { return cend(); } MWWorld::ContainerStoreIterator MWWorld::ContainerStore::begin(int mask) { return ContainerStoreIterator(mask, this); } MWWorld::ContainerStoreIterator MWWorld::ContainerStore::end() { return ContainerStoreIterator(this); } int MWWorld::ContainerStore::count(const ESM::RefId& id) const { int total = 0; for (const auto&& iter : *this) if (iter.getCellRef().getRefId() == id) total += iter.getRefData().getCount(); return total; } void MWWorld::ContainerStore::clearRefNums() { for (const auto& iter : *this) iter.getCellRef().unsetRefNum(); } MWWorld::ContainerStoreListener* MWWorld::ContainerStore::getContListener() const { return mListener; } void MWWorld::ContainerStore::setContListener(MWWorld::ContainerStoreListener* listener) { mListener = listener; } MWWorld::ContainerStoreIterator MWWorld::ContainerStore::unstack(const Ptr& ptr, int count) { resolve(); if (ptr.getRefData().getCount() <= count) return end(); MWWorld::ContainerStoreIterator it = addNewStack(ptr, subtractItems(ptr.getRefData().getCount(false), count)); MWWorld::Ptr newPtr = *it; newPtr.getCellRef().unsetRefNum(); newPtr.getRefData().setLuaScripts(nullptr); MWBase::Environment::get().getWorldModel()->registerPtr(newPtr); const ESM::RefId& script = it->getClass().getScript(*it); if (!script.empty()) MWBase::Environment::get().getWorld()->getLocalScripts().add(script, *it); remove(ptr, ptr.getRefData().getCount() - count); return it; } MWWorld::ContainerStoreIterator MWWorld::ContainerStore::restack(const MWWorld::Ptr& item) { resolve(); MWWorld::ContainerStoreIterator retval = end(); for (MWWorld::ContainerStoreIterator iter(begin()); iter != end(); ++iter) { if (item == *iter) { retval = iter; break; } } if (retval == end()) throw std::runtime_error("item is not from this container"); for (MWWorld::ContainerStoreIterator iter(begin()); iter != end(); ++iter) { if (stacks(*iter, item)) { iter->getRefData().setCount( addItems(iter->getRefData().getCount(false), item.getRefData().getCount(false))); item.getRefData().setCount(0); retval = iter; break; } } return retval; } bool MWWorld::ContainerStore::stacks(const ConstPtr& ptr1, const ConstPtr& ptr2) const { const MWWorld::Class& cls1 = ptr1.getClass(); const MWWorld::Class& cls2 = ptr2.getClass(); if (!(ptr1.getCellRef().getRefId() == ptr2.getCellRef().getRefId())) return false; // If it has an enchantment, don't stack when some of the charge is already used if (!ptr1.getClass().getEnchantment(ptr1).empty()) { const ESM::Enchantment* enchantment = MWBase::Environment::get().getESMStore()->get().find( ptr1.getClass().getEnchantment(ptr1)); const float maxCharge = static_cast(MWMechanics::getEnchantmentCharge(*enchantment)); float enchantCharge1 = ptr1.getCellRef().getEnchantmentCharge() == -1 ? maxCharge : ptr1.getCellRef().getEnchantmentCharge(); float enchantCharge2 = ptr2.getCellRef().getEnchantmentCharge() == -1 ? maxCharge : ptr2.getCellRef().getEnchantmentCharge(); if (enchantCharge1 != maxCharge || enchantCharge2 != maxCharge) return false; } return ptr1 != ptr2 // an item never stacks onto itself && ptr1.getCellRef().getSoul() == ptr2.getCellRef().getSoul() && ptr1.getClass().getRemainingUsageTime(ptr1) == ptr2.getClass().getRemainingUsageTime(ptr2) // Items with scripts never stack && cls1.getScript(ptr1).empty() && cls2.getScript(ptr2).empty() // item that is already partly used up never stacks && (!cls1.hasItemHealth(ptr1) || (cls1.getItemHealth(ptr1) == cls1.getItemMaxHealth(ptr1) && cls2.getItemHealth(ptr2) == cls2.getItemMaxHealth(ptr2))); } MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add(const ESM::RefId& id, int count) { MWWorld::ManualRef ref(*MWBase::Environment::get().getESMStore(), id, count); return add(ref.getPtr(), count); } MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add( const Ptr& itemPtr, int count, bool /*allowAutoEquip*/, bool resolve) { Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWWorld::ContainerStoreIterator it = addImp(itemPtr, count, resolve); itemPtr.getRefData().setLuaScripts(nullptr); // clear Lua scripts on the original (removed) item. // The copy of the original item we just made MWWorld::Ptr item = *it; MWBase::Environment::get().getWorldModel()->registerPtr(item); // we may have copied an item from the world, so reset a few things first item.getRefData().setBaseNode( nullptr); // Especially important, otherwise scripts on the item could think that it's actually in a cell ESM::Position pos; pos.rot[0] = 0; pos.rot[1] = 0; pos.rot[2] = 0; pos.pos[0] = 0; pos.pos[1] = 0; pos.pos[2] = 0; item.getCellRef().setPosition(pos); // We do not need to store owners for items in container stores - we do not use it anyway. item.getCellRef().setOwner(ESM::RefId()); item.getCellRef().resetGlobalVariable(); item.getCellRef().setFaction(ESM::RefId()); item.getCellRef().setFactionRank(-2); const ESM::RefId& script = item.getClass().getScript(item); if (!script.empty()) { if (mActor == player) { // Items in player's inventory have cell set to 0, so their scripts will never be removed item.mCell = nullptr; } else { // Set mCell to the cell of the container/actor, so that the scripts are removed properly when // the cell of the container/actor goes inactive if (!mPtr.isEmpty()) item.mCell = mPtr.getCell(); else if (!mActor.isEmpty()) item.mCell = mActor.getCell(); } item.mContainerStore = this; MWBase::Environment::get().getWorld()->getLocalScripts().add(script, item); // Set OnPCAdd special variable, if it is declared // Make sure to do this *after* we have added the script to LocalScripts if (mActor == player) item.getRefData().getLocals().setVarByInt(script, "onpcadd", 1); } // we should not fire event for InventoryStore yet - it has some custom logic if (mListener && !(!mActor.isEmpty() && mActor.getClass().hasInventoryStore(mActor))) mListener->itemAdded(item, count); return it; } MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp(const Ptr& ptr, int count, bool markModified) { if (markModified) resolve(); int type = getType(ptr); const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore(); // gold needs special handling: when it is inserted into a container, the base object automatically becomes Gold_001 // this ensures that gold piles of different sizes stack with each other (also, several scripts rely on Gold_001 for // detecting player gold) if (ptr.getClass().isGold(ptr)) { int realCount = count * ptr.getClass().getValue(ptr); for (MWWorld::ContainerStoreIterator iter(begin(type)); iter != end(); ++iter) { if (iter->getCellRef().getRefId() == MWWorld::ContainerStore::sGoldId) { iter->getRefData().setCount(addItems(iter->getRefData().getCount(false), realCount)); flagAsModified(); return iter; } } MWWorld::ManualRef ref(esmStore, MWWorld::ContainerStore::sGoldId, realCount); return addNewStack(ref.getPtr(), realCount); } // determine whether to stack or not for (MWWorld::ContainerStoreIterator iter(begin(type)); iter != end(); ++iter) { // Don't stack with equipped items if (!mActor.isEmpty() && mActor.getClass().hasInventoryStore(mActor)) if (mActor.getClass().getInventoryStore(mActor).isEquipped(*iter)) continue; if (stacks(*iter, ptr)) { // stack iter->getRefData().setCount(addItems(iter->getRefData().getCount(false), count)); flagAsModified(); return iter; } } // if we got here, this means no stacking return addNewStack(ptr, count); } MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addNewStack(const ConstPtr& ptr, int count) { ContainerStoreIterator it = begin(); switch (getType(ptr)) { case Type_Potion: potions.mList.push_back(*ptr.get()); it = ContainerStoreIterator(this, --potions.mList.end()); break; case Type_Apparatus: appas.mList.push_back(*ptr.get()); it = ContainerStoreIterator(this, --appas.mList.end()); break; case Type_Armor: armors.mList.push_back(*ptr.get()); it = ContainerStoreIterator(this, --armors.mList.end()); break; case Type_Book: books.mList.push_back(*ptr.get()); it = ContainerStoreIterator(this, --books.mList.end()); break; case Type_Clothing: clothes.mList.push_back(*ptr.get()); it = ContainerStoreIterator(this, --clothes.mList.end()); break; case Type_Ingredient: ingreds.mList.push_back(*ptr.get()); it = ContainerStoreIterator(this, --ingreds.mList.end()); break; case Type_Light: lights.mList.push_back(*ptr.get()); it = ContainerStoreIterator(this, --lights.mList.end()); break; case Type_Lockpick: lockpicks.mList.push_back(*ptr.get()); it = ContainerStoreIterator(this, --lockpicks.mList.end()); break; case Type_Miscellaneous: miscItems.mList.push_back(*ptr.get()); it = ContainerStoreIterator(this, --miscItems.mList.end()); break; case Type_Probe: probes.mList.push_back(*ptr.get()); it = ContainerStoreIterator(this, --probes.mList.end()); break; case Type_Repair: repairs.mList.push_back(*ptr.get()); it = ContainerStoreIterator(this, --repairs.mList.end()); break; case Type_Weapon: weapons.mList.push_back(*ptr.get()); it = ContainerStoreIterator(this, --weapons.mList.end()); break; } it->getRefData().setCount(count); flagAsModified(); return it; } void MWWorld::ContainerStore::rechargeItems(float duration) { if (!mRechargingItemsUpToDate) { updateRechargingItems(); mRechargingItemsUpToDate = true; } for (auto& it : mRechargingItems) { if (!MWMechanics::rechargeItem(*it.first, it.second, duration)) continue; // attempt to restack when fully recharged if (it.first->getCellRef().getEnchantmentCharge() == it.second) it.first = restack(*it.first); } } void MWWorld::ContainerStore::updateRechargingItems() { mRechargingItems.clear(); for (ContainerStoreIterator it = begin(); it != end(); ++it) { const auto& enchantmentId = it->getClass().getEnchantment(*it); if (!enchantmentId.empty()) { const ESM::Enchantment* enchantment = MWBase::Environment::get().getESMStore()->get().search(enchantmentId); if (!enchantment) { Log(Debug::Warning) << "Warning: Can't find enchantment '" << enchantmentId << "' on item " << it->getCellRef().getRefId(); continue; } if (enchantment->mData.mType == ESM::Enchantment::WhenUsed || enchantment->mData.mType == ESM::Enchantment::WhenStrikes) mRechargingItems.emplace_back(it, static_cast(MWMechanics::getEnchantmentCharge(*enchantment))); } } } int MWWorld::ContainerStore::remove(const ESM::RefId& itemId, int count, bool equipReplacement, bool resolveFirst) { if (resolveFirst) resolve(); int toRemove = count; for (ContainerStoreIterator iter(begin()); iter != end() && toRemove > 0; ++iter) if (iter->getCellRef().getRefId() == itemId) toRemove -= remove(*iter, toRemove, equipReplacement, resolveFirst); flagAsModified(); // number of removed items return count - toRemove; } bool MWWorld::ContainerStore::hasVisibleItems() const { for (const auto&& iter : *this) { if (iter.getClass().showsInInventory(iter)) return true; } return false; } int MWWorld::ContainerStore::remove(const Ptr& item, int count, bool equipReplacement, bool resolveFirst) { assert(this == item.getContainerStore()); if (resolveFirst) resolve(); int toRemove = count; RefData& itemRef = item.getRefData(); if (itemRef.getCount() <= toRemove) { toRemove -= itemRef.getCount(); itemRef.setCount(0); } else { itemRef.setCount(subtractItems(itemRef.getCount(false), toRemove)); toRemove = 0; } flagAsModified(); // we should not fire event for InventoryStore yet - it has some custom logic if (mListener && !(!mActor.isEmpty() && mActor.getClass().hasInventoryStore(mActor))) mListener->itemRemoved(item, count - toRemove); // number of removed items return count - toRemove; } void MWWorld::ContainerStore::fill(const ESM::InventoryList& items, const ESM::RefId& owner, Misc::Rng::Generator& prng) { for (const ESM::ContItem& iter : items.mList) { addInitialItem(iter.mItem, owner, iter.mCount, &prng); } flagAsModified(); mResolved = true; } void MWWorld::ContainerStore::fillNonRandom(const ESM::InventoryList& items, const ESM::RefId& owner, unsigned int seed) { mSeed = seed; for (const ESM::ContItem& iter : items.mList) { addInitialItem(iter.mItem, owner, iter.mCount, nullptr); } flagAsModified(); mResolved = false; } void MWWorld::ContainerStore::addInitialItem( const ESM::RefId& id, const ESM::RefId& owner, int count, Misc::Rng::Generator* prng, bool topLevel) { if (count == 0) return; // Don't restock with nothing. try { ManualRef ref(*MWBase::Environment::get().getESMStore(), id, count); if (ref.getPtr().getClass().getScript(ref.getPtr()).empty()) { addInitialItemImp(ref.getPtr(), owner, count, prng, topLevel); } else { // Adding just one item per time to make sure there isn't a stack of scripted items for (int i = 0; i < std::abs(count); i++) addInitialItemImp(ref.getPtr(), owner, count < 0 ? -1 : 1, prng, topLevel); } } catch (const std::exception& e) { Log(Debug::Warning) << "Warning: MWWorld::ContainerStore::addInitialItem: " << e.what(); } } void MWWorld::ContainerStore::addInitialItemImp( const MWWorld::Ptr& ptr, const ESM::RefId& owner, int count, Misc::Rng::Generator* prng, bool topLevel) { if (ptr.getType() == ESM::ItemLevList::sRecordId) { if (!prng) return; const ESM::ItemLevList* levItemList = ptr.get()->mBase; if (topLevel && std::abs(count) > 1 && levItemList->mFlags & ESM::ItemLevList::Each) { for (int i = 0; i < std::abs(count); ++i) addInitialItem(ptr.getCellRef().getRefId(), owner, count > 0 ? 1 : -1, prng, true); return; } else { const auto& itemId = MWMechanics::getLevelledItem(ptr.get()->mBase, false, *prng); if (itemId.empty()) return; addInitialItem(itemId, owner, count, prng, false); } } else { ptr.getCellRef().setOwner(owner); addImp(ptr, count, false); } } void MWWorld::ContainerStore::clear() { for (auto&& iter : *this) iter.getRefData().setCount(0); flagAsModified(); mModified = true; } void MWWorld::ContainerStore::flagAsModified() { mWeightUpToDate = false; mRechargingItemsUpToDate = false; } bool MWWorld::ContainerStore::isResolved() const { return mResolved; } void MWWorld::ContainerStore::resolve() { if (!mResolved && !mPtr.isEmpty()) { for (const auto&& ptr : *this) ptr.getRefData().setCount(0); Misc::Rng::Generator prng{ mSeed }; fill(mPtr.get()->mBase->mInventory, ESM::RefId(), prng); addScripts(*this, mPtr.mCell); } mModified = true; } MWWorld::ResolutionHandle MWWorld::ContainerStore::resolveTemporarily() { if (mModified) return {}; std::shared_ptr listener = mResolutionListener.lock(); if (!listener) { listener = std::make_shared(*this); mResolutionListener = listener; } if (!mResolved && !mPtr.isEmpty()) { for (const auto&& ptr : *this) ptr.getRefData().setCount(0); Misc::Rng::Generator prng{ mSeed }; fill(mPtr.get()->mBase->mInventory, ESM::RefId(), prng); addScripts(*this, mPtr.mCell); } return { listener }; } void MWWorld::ContainerStore::unresolve() { if (mModified) return; if (mResolved && !mPtr.isEmpty()) { for (const auto&& ptr : *this) ptr.getRefData().setCount(0); fillNonRandom(mPtr.get()->mBase->mInventory, ESM::RefId(), mSeed); addScripts(*this, mPtr.mCell); mResolved = false; } } float MWWorld::ContainerStore::getWeight() const { if (!mWeightUpToDate) { mCachedWeight = 0; mCachedWeight += getTotalWeight(potions); mCachedWeight += getTotalWeight(appas); mCachedWeight += getTotalWeight(armors); mCachedWeight += getTotalWeight(books); mCachedWeight += getTotalWeight(clothes); mCachedWeight += getTotalWeight(ingreds); mCachedWeight += getTotalWeight(lights); mCachedWeight += getTotalWeight(lockpicks); mCachedWeight += getTotalWeight(miscItems); mCachedWeight += getTotalWeight(probes); mCachedWeight += getTotalWeight(repairs); mCachedWeight += getTotalWeight(weapons); mWeightUpToDate = true; } return mCachedWeight; } int MWWorld::ContainerStore::getType(const ConstPtr& ptr) { if (ptr.isEmpty()) throw std::runtime_error("can't put a non-existent object into a container"); if (ptr.getType() == ESM::Potion::sRecordId) return Type_Potion; if (ptr.getType() == ESM::Apparatus::sRecordId) return Type_Apparatus; if (ptr.getType() == ESM::Armor::sRecordId) return Type_Armor; if (ptr.getType() == ESM::Book::sRecordId) return Type_Book; if (ptr.getType() == ESM::Clothing::sRecordId) return Type_Clothing; if (ptr.getType() == ESM::Ingredient::sRecordId) return Type_Ingredient; if (ptr.getType() == ESM::Light::sRecordId) return Type_Light; if (ptr.getType() == ESM::Lockpick::sRecordId) return Type_Lockpick; if (ptr.getType() == ESM::Miscellaneous::sRecordId) return Type_Miscellaneous; if (ptr.getType() == ESM::Probe::sRecordId) return Type_Probe; if (ptr.getType() == ESM::Repair::sRecordId) return Type_Repair; if (ptr.getType() == ESM::Weapon::sRecordId) return Type_Weapon; throw std::runtime_error("Object " + ptr.getCellRef().getRefId().toDebugString() + " of type " + std::string(ptr.getTypeDescription()) + " can not be placed into a container"); } MWWorld::Ptr MWWorld::ContainerStore::findReplacement(const ESM::RefId& id) { MWWorld::Ptr item; int itemHealth = 1; for (auto&& iter : *this) { int iterHealth = iter.getClass().hasItemHealth(iter) ? iter.getClass().getItemHealth(iter) : 1; if (iter.getCellRef().getRefId() == id) { // Prefer the stack with the lowest remaining uses // Try to get item with zero durability only if there are no other items found if (item.isEmpty() || (iterHealth > 0 && iterHealth < itemHealth) || (itemHealth <= 0 && iterHealth > 0)) { item = iter; itemHealth = iterHealth; } } } return item; } MWWorld::Ptr MWWorld::ContainerStore::search(const ESM::RefId& id) { resolve(); { Ptr ptr = searchId(potions, id, this); if (!ptr.isEmpty()) return ptr; } { Ptr ptr = searchId(appas, id, this); if (!ptr.isEmpty()) return ptr; } { Ptr ptr = searchId(armors, id, this); if (!ptr.isEmpty()) return ptr; } { Ptr ptr = searchId(books, id, this); if (!ptr.isEmpty()) return ptr; } { Ptr ptr = searchId(clothes, id, this); if (!ptr.isEmpty()) return ptr; } { Ptr ptr = searchId(ingreds, id, this); if (!ptr.isEmpty()) return ptr; } { Ptr ptr = searchId(lights, id, this); if (!ptr.isEmpty()) return ptr; } { Ptr ptr = searchId(lockpicks, id, this); if (!ptr.isEmpty()) return ptr; } { Ptr ptr = searchId(miscItems, id, this); if (!ptr.isEmpty()) return ptr; } { Ptr ptr = searchId(probes, id, this); if (!ptr.isEmpty()) return ptr; } { Ptr ptr = searchId(repairs, id, this); if (!ptr.isEmpty()) return ptr; } { Ptr ptr = searchId(weapons, id, this); if (!ptr.isEmpty()) return ptr; } return Ptr(); } int MWWorld::ContainerStore::addItems(int count1, int count2) { int sum = std::abs(count1) + std::abs(count2); if (count1 < 0 || count2 < 0) return -sum; return sum; } int MWWorld::ContainerStore::subtractItems(int count1, int count2) { int sum = std::abs(count1) - std::abs(count2); if (count1 < 0 || count2 < 0) return -sum; return sum; } void MWWorld::ContainerStore::writeState(ESM::InventoryState& state) const { state.mItems.clear(); int index = 0; storeStates(potions, state, index); storeStates(appas, state, index); storeStates(armors, state, index, true); storeStates(books, state, index, true); // not equipable as such, but for selectedEnchantItem storeStates(clothes, state, index, true); storeStates(ingreds, state, index); storeStates(lockpicks, state, index, true); storeStates(miscItems, state, index); storeStates(probes, state, index, true); storeStates(repairs, state, index); storeStates(weapons, state, index, true); storeStates(lights, state, index, true); } void MWWorld::ContainerStore::readState(const ESM::InventoryState& inventory) { clear(); mModified = true; mResolved = true; int index = 0; for (const ESM::ObjectState& state : inventory.mItems) { int type = MWBase::Environment::get().getESMStore()->find(state.mRef.mRefID); int thisIndex = index++; switch (type) { case ESM::REC_ALCH: getState(potions, state); break; case ESM::REC_APPA: getState(appas, state); break; case ESM::REC_ARMO: readEquipmentState(getState(armors, state), thisIndex, inventory); break; case ESM::REC_BOOK: readEquipmentState(getState(books, state), thisIndex, inventory); break; // not equipable as such, but for selectedEnchantItem case ESM::REC_CLOT: readEquipmentState(getState(clothes, state), thisIndex, inventory); break; case ESM::REC_INGR: getState(ingreds, state); break; case ESM::REC_LOCK: readEquipmentState(getState(lockpicks, state), thisIndex, inventory); break; case ESM::REC_MISC: getState(miscItems, state); break; case ESM::REC_PROB: readEquipmentState(getState(probes, state), thisIndex, inventory); break; case ESM::REC_REPA: getState(repairs, state); break; case ESM::REC_WEAP: readEquipmentState(getState(weapons, state), thisIndex, inventory); break; case ESM::REC_LIGH: readEquipmentState(getState(lights, state), thisIndex, inventory); break; case 0: Log(Debug::Warning) << "Dropping inventory reference to '" << state.mRef.mRefID << "' (object no longer exists)"; break; default: Log(Debug::Warning) << "Warning: Invalid item type in inventory state, refid " << state.mRef.mRefID; break; } } } template template void MWWorld::ContainerStoreIteratorBase::copy(const ContainerStoreIteratorBase& src) { mType = src.mType; mMask = src.mMask; mContainer = src.mContainer; mPtr = src.mPtr; switch (src.mType) { case MWWorld::ContainerStore::Type_Potion: mPotion = src.mPotion; break; case MWWorld::ContainerStore::Type_Apparatus: mApparatus = src.mApparatus; break; case MWWorld::ContainerStore::Type_Armor: mArmor = src.mArmor; break; case MWWorld::ContainerStore::Type_Book: mBook = src.mBook; break; case MWWorld::ContainerStore::Type_Clothing: mClothing = src.mClothing; break; case MWWorld::ContainerStore::Type_Ingredient: mIngredient = src.mIngredient; break; case MWWorld::ContainerStore::Type_Light: mLight = src.mLight; break; case MWWorld::ContainerStore::Type_Lockpick: mLockpick = src.mLockpick; break; case MWWorld::ContainerStore::Type_Miscellaneous: mMiscellaneous = src.mMiscellaneous; break; case MWWorld::ContainerStore::Type_Probe: mProbe = src.mProbe; break; case MWWorld::ContainerStore::Type_Repair: mRepair = src.mRepair; break; case MWWorld::ContainerStore::Type_Weapon: mWeapon = src.mWeapon; break; case -1: break; default: assert(0); } } template void MWWorld::ContainerStoreIteratorBase::incType() { if (mType == 0) mType = 1; else if (mType != -1) { mType <<= 1; if (mType > ContainerStore::Type_Last) mType = -1; } } template void MWWorld::ContainerStoreIteratorBase::nextType() { while (mType != -1) { incType(); if ((mType & mMask) && mType > 0) if (resetIterator()) break; } } template bool MWWorld::ContainerStoreIteratorBase::resetIterator() { switch (mType) { case ContainerStore::Type_Potion: mPotion = mContainer->potions.mList.begin(); return mPotion != mContainer->potions.mList.end(); case ContainerStore::Type_Apparatus: mApparatus = mContainer->appas.mList.begin(); return mApparatus != mContainer->appas.mList.end(); case ContainerStore::Type_Armor: mArmor = mContainer->armors.mList.begin(); return mArmor != mContainer->armors.mList.end(); case ContainerStore::Type_Book: mBook = mContainer->books.mList.begin(); return mBook != mContainer->books.mList.end(); case ContainerStore::Type_Clothing: mClothing = mContainer->clothes.mList.begin(); return mClothing != mContainer->clothes.mList.end(); case ContainerStore::Type_Ingredient: mIngredient = mContainer->ingreds.mList.begin(); return mIngredient != mContainer->ingreds.mList.end(); case ContainerStore::Type_Light: mLight = mContainer->lights.mList.begin(); return mLight != mContainer->lights.mList.end(); case ContainerStore::Type_Lockpick: mLockpick = mContainer->lockpicks.mList.begin(); return mLockpick != mContainer->lockpicks.mList.end(); case ContainerStore::Type_Miscellaneous: mMiscellaneous = mContainer->miscItems.mList.begin(); return mMiscellaneous != mContainer->miscItems.mList.end(); case ContainerStore::Type_Probe: mProbe = mContainer->probes.mList.begin(); return mProbe != mContainer->probes.mList.end(); case ContainerStore::Type_Repair: mRepair = mContainer->repairs.mList.begin(); return mRepair != mContainer->repairs.mList.end(); case ContainerStore::Type_Weapon: mWeapon = mContainer->weapons.mList.begin(); return mWeapon != mContainer->weapons.mList.end(); } return false; } template bool MWWorld::ContainerStoreIteratorBase::incIterator() { switch (mType) { case ContainerStore::Type_Potion: ++mPotion; return mPotion == mContainer->potions.mList.end(); case ContainerStore::Type_Apparatus: ++mApparatus; return mApparatus == mContainer->appas.mList.end(); case ContainerStore::Type_Armor: ++mArmor; return mArmor == mContainer->armors.mList.end(); case ContainerStore::Type_Book: ++mBook; return mBook == mContainer->books.mList.end(); case ContainerStore::Type_Clothing: ++mClothing; return mClothing == mContainer->clothes.mList.end(); case ContainerStore::Type_Ingredient: ++mIngredient; return mIngredient == mContainer->ingreds.mList.end(); case ContainerStore::Type_Light: ++mLight; return mLight == mContainer->lights.mList.end(); case ContainerStore::Type_Lockpick: ++mLockpick; return mLockpick == mContainer->lockpicks.mList.end(); case ContainerStore::Type_Miscellaneous: ++mMiscellaneous; return mMiscellaneous == mContainer->miscItems.mList.end(); case ContainerStore::Type_Probe: ++mProbe; return mProbe == mContainer->probes.mList.end(); case ContainerStore::Type_Repair: ++mRepair; return mRepair == mContainer->repairs.mList.end(); case ContainerStore::Type_Weapon: ++mWeapon; return mWeapon == mContainer->weapons.mList.end(); } return true; } template template bool MWWorld::ContainerStoreIteratorBase::isEqual(const ContainerStoreIteratorBase& other) const { if (mContainer != other.mContainer) return false; if (mType != other.mType) return false; switch (mType) { case ContainerStore::Type_Potion: return mPotion == other.mPotion; case ContainerStore::Type_Apparatus: return mApparatus == other.mApparatus; case ContainerStore::Type_Armor: return mArmor == other.mArmor; case ContainerStore::Type_Book: return mBook == other.mBook; case ContainerStore::Type_Clothing: return mClothing == other.mClothing; case ContainerStore::Type_Ingredient: return mIngredient == other.mIngredient; case ContainerStore::Type_Light: return mLight == other.mLight; case ContainerStore::Type_Lockpick: return mLockpick == other.mLockpick; case ContainerStore::Type_Miscellaneous: return mMiscellaneous == other.mMiscellaneous; case ContainerStore::Type_Probe: return mProbe == other.mProbe; case ContainerStore::Type_Repair: return mRepair == other.mRepair; case ContainerStore::Type_Weapon: return mWeapon == other.mWeapon; case -1: return true; } return false; } template PtrType* MWWorld::ContainerStoreIteratorBase::operator->() const { mPtr = **this; return &mPtr; } template PtrType MWWorld::ContainerStoreIteratorBase::operator*() const { PtrType ptr; switch (mType) { case ContainerStore::Type_Potion: ptr = PtrType(&*mPotion, nullptr); break; case ContainerStore::Type_Apparatus: ptr = PtrType(&*mApparatus, nullptr); break; case ContainerStore::Type_Armor: ptr = PtrType(&*mArmor, nullptr); break; case ContainerStore::Type_Book: ptr = PtrType(&*mBook, nullptr); break; case ContainerStore::Type_Clothing: ptr = PtrType(&*mClothing, nullptr); break; case ContainerStore::Type_Ingredient: ptr = PtrType(&*mIngredient, nullptr); break; case ContainerStore::Type_Light: ptr = PtrType(&*mLight, nullptr); break; case ContainerStore::Type_Lockpick: ptr = PtrType(&*mLockpick, nullptr); break; case ContainerStore::Type_Miscellaneous: ptr = PtrType(&*mMiscellaneous, nullptr); break; case ContainerStore::Type_Probe: ptr = PtrType(&*mProbe, nullptr); break; case ContainerStore::Type_Repair: ptr = PtrType(&*mRepair, nullptr); break; case ContainerStore::Type_Weapon: ptr = PtrType(&*mWeapon, nullptr); break; } if (ptr.isEmpty()) throw std::runtime_error("invalid iterator"); ptr.setContainerStore(mContainer); return ptr; } template MWWorld::ContainerStoreIteratorBase& MWWorld::ContainerStoreIteratorBase::operator++() { do { if (incIterator()) nextType(); } while (mType != -1 && !(**this).getRefData().getCount()); return *this; } template MWWorld::ContainerStoreIteratorBase MWWorld::ContainerStoreIteratorBase::operator++(int) { ContainerStoreIteratorBase iter(*this); ++*this; return iter; } template MWWorld::ContainerStoreIteratorBase& MWWorld::ContainerStoreIteratorBase::operator=( const ContainerStoreIteratorBase& rhs) { if (this != &rhs) { copy(rhs); } return *this; } template int MWWorld::ContainerStoreIteratorBase::getType() const { return mType; } template const MWWorld::ContainerStore* MWWorld::ContainerStoreIteratorBase::getContainerStore() const { return mContainer; } template MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase(ContainerStoreType container) : mType(-1) , mMask(0) , mContainer(container) { } template MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase(int mask, ContainerStoreType container) : mType(0) , mMask(mask) , mContainer(container) { nextType(); if (mType == -1 || (**this).getRefData().getCount()) return; ++*this; } template MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase( ContainerStoreType container, typename Iterator::type iterator) : mType(MWWorld::ContainerStore::Type_Potion) , mMask(MWWorld::ContainerStore::Type_All) , mContainer(container) , mPotion(iterator) { } template MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase( ContainerStoreType container, typename Iterator::type iterator) : mType(MWWorld::ContainerStore::Type_Apparatus) , mMask(MWWorld::ContainerStore::Type_All) , mContainer(container) , mApparatus(iterator) { } template MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase( ContainerStoreType container, typename Iterator::type iterator) : mType(MWWorld::ContainerStore::Type_Armor) , mMask(MWWorld::ContainerStore::Type_All) , mContainer(container) , mArmor(iterator) { } template MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase( ContainerStoreType container, typename Iterator::type iterator) : mType(MWWorld::ContainerStore::Type_Book) , mMask(MWWorld::ContainerStore::Type_All) , mContainer(container) , mBook(iterator) { } template MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase( ContainerStoreType container, typename Iterator::type iterator) : mType(MWWorld::ContainerStore::Type_Clothing) , mMask(MWWorld::ContainerStore::Type_All) , mContainer(container) , mClothing(iterator) { } template MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase( ContainerStoreType container, typename Iterator::type iterator) : mType(MWWorld::ContainerStore::Type_Ingredient) , mMask(MWWorld::ContainerStore::Type_All) , mContainer(container) , mIngredient(iterator) { } template MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase( ContainerStoreType container, typename Iterator::type iterator) : mType(MWWorld::ContainerStore::Type_Light) , mMask(MWWorld::ContainerStore::Type_All) , mContainer(container) , mLight(iterator) { } template MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase( ContainerStoreType container, typename Iterator::type iterator) : mType(MWWorld::ContainerStore::Type_Lockpick) , mMask(MWWorld::ContainerStore::Type_All) , mContainer(container) , mLockpick(iterator) { } template MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase( ContainerStoreType container, typename Iterator::type iterator) : mType(MWWorld::ContainerStore::Type_Miscellaneous) , mMask(MWWorld::ContainerStore::Type_All) , mContainer(container) , mMiscellaneous(iterator) { } template MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase( ContainerStoreType container, typename Iterator::type iterator) : mType(MWWorld::ContainerStore::Type_Probe) , mMask(MWWorld::ContainerStore::Type_All) , mContainer(container) , mProbe(iterator) { } template MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase( ContainerStoreType container, typename Iterator::type iterator) : mType(MWWorld::ContainerStore::Type_Repair) , mMask(MWWorld::ContainerStore::Type_All) , mContainer(container) , mRepair(iterator) { } template MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase( ContainerStoreType container, typename Iterator::type iterator) : mType(MWWorld::ContainerStore::Type_Weapon) , mMask(MWWorld::ContainerStore::Type_All) , mContainer(container) , mWeapon(iterator) { } template bool MWWorld::operator==(const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right) { return left.isEqual(right); } template bool MWWorld::operator!=(const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right) { return !(left == right); } template class MWWorld::ContainerStoreIteratorBase; template class MWWorld::ContainerStoreIteratorBase; template bool MWWorld::operator==( const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right); template bool MWWorld::operator!=( const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right); template bool MWWorld::operator==( const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right); template bool MWWorld::operator!=( const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right); template bool MWWorld::operator==( const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right); template bool MWWorld::operator!=( const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right); template bool MWWorld::operator==( const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right); template bool MWWorld::operator!=( const ContainerStoreIteratorBase& left, const ContainerStoreIteratorBase& right); template void MWWorld::ContainerStoreIteratorBase::copy(const ContainerStoreIteratorBase& src); template void MWWorld::ContainerStoreIteratorBase::copy(const ContainerStoreIteratorBase& src); template void MWWorld::ContainerStoreIteratorBase::copy( const ContainerStoreIteratorBase& src);