From 72549651e0b3afcbf2a481b8db2731281b760e41 Mon Sep 17 00:00:00 2001 From: Assumeru Date: Tue, 13 Oct 2020 17:46:32 +0200 Subject: [PATCH] Rework container resolution (#3006) * Rework container resolution * add optional argument to getCount * remove now-redundant changes * undo worldimp changes * move save-fixing code to InventoryState * replace Rng instances with Seeds --- CHANGELOG.md | 3 + apps/openmw/mwclass/container.cpp | 83 ++---- apps/openmw/mwclass/container.hpp | 25 +- apps/openmw/mwclass/creature.cpp | 8 - apps/openmw/mwclass/creature.hpp | 2 - apps/openmw/mwclass/npc.cpp | 8 - apps/openmw/mwclass/npc.hpp | 2 - apps/openmw/mwgui/container.cpp | 1 + apps/openmw/mwgui/containeritemmodel.cpp | 49 +-- apps/openmw/mwgui/containeritemmodel.hpp | 9 +- apps/openmw/mwgui/tradeitemmodel.cpp | 2 +- apps/openmw/mwgui/tradeitemmodel.hpp | 2 +- apps/openmw/mwgui/tradewindow.cpp | 35 +-- apps/openmw/mwgui/tradewindow.hpp | 3 +- apps/openmw/mwmechanics/levelledlist.hpp | 10 +- .../mwmechanics/mechanicsmanagerimp.cpp | 3 +- apps/openmw/mwworld/actionharvest.cpp | 1 + apps/openmw/mwworld/cellstore.cpp | 3 +- apps/openmw/mwworld/class.hpp | 2 - apps/openmw/mwworld/containerstore.cpp | 278 +++++++++--------- apps/openmw/mwworld/containerstore.hpp | 63 +++- apps/openmw/mwworld/inventorystore.cpp | 9 +- apps/openmw/mwworld/localscripts.cpp | 2 +- apps/openmw/mwworld/refdata.cpp | 4 +- apps/openmw/mwworld/refdata.hpp | 2 +- .../findrandompointaroundcircle.cpp | 2 +- components/esm/inventorystate.cpp | 15 + components/esm/savedgame.cpp | 2 +- components/misc/rng.cpp | 31 +- components/misc/rng.hpp | 20 +- 30 files changed, 371 insertions(+), 308 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9813cabff..80253e99c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,11 @@ Bug #1952: Incorrect particle lighting Bug #2069: Fireflies in Fireflies invade Morrowind look wrong Bug #2311: Targeted scripts are not properly supported on non-unique RefIDs + Bug #2473: Unable to overstock merchants Bug #3676: NiParticleColorModifier isn't applied properly Bug #3714: Savegame fails to load due to conflict between SpellState and MagicEffects + Bug #3862: Random container contents behave differently than vanilla + Bug #3929: Leveled list merchant containers respawn on barter Bug #4021: Attributes and skills are not stored as floats Bug #4055: Local scripts don't inherit variables from their base record Bug #4623: Corprus implementation is incorrect diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index b4b068c91..807f2299b 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -31,44 +31,41 @@ namespace MWClass { - class ContainerCustomData : public MWWorld::CustomData + ContainerCustomData::ContainerCustomData(const ESM::Container& container, MWWorld::CellStore* cell) { - public: - MWWorld::ContainerStore mContainerStore; + unsigned int seed = Misc::Rng::rollDice(std::numeric_limits::max()); + // setting ownership not needed, since taking items from a container inherits the + // container's owner automatically + mStore.fillNonRandom(container.mInventory, "", seed); + } - virtual MWWorld::CustomData *clone() const; - - virtual ContainerCustomData& asContainerCustomData() - { - return *this; - } - virtual const ContainerCustomData& asContainerCustomData() const - { - return *this; - } - }; + ContainerCustomData::ContainerCustomData(const ESM::InventoryState& inventory) + { + mStore.readState(inventory); + } MWWorld::CustomData *ContainerCustomData::clone() const { return new ContainerCustomData (*this); } + ContainerCustomData& ContainerCustomData::asContainerCustomData() + { + return *this; + } + const ContainerCustomData& ContainerCustomData::asContainerCustomData() const + { + return *this; + } + void Container::ensureCustomData (const MWWorld::Ptr& ptr) const { if (!ptr.getRefData().getCustomData()) { - std::unique_ptr data (new ContainerCustomData); - - MWWorld::LiveCellRef *ref = - ptr.get(); - - // setting ownership not needed, since taking items from a container inherits the - // container's owner automatically - data->mContainerStore.fill( - ref->mBase->mInventory, ""); + MWWorld::LiveCellRef *ref = ptr.get(); // store - ptr.getRefData().setCustomData (data.release()); + ptr.getRefData().setCustomData (std::make_unique(*ref->mBase, ptr.getCell()).release()); MWBase::Environment::get().getWorld()->addContainerScripts(ptr, ptr.getCell()); } @@ -98,17 +95,6 @@ namespace MWClass } } - void Container::restock(const MWWorld::Ptr& ptr) const - { - MWWorld::LiveCellRef *ref = ptr.get(); - const ESM::InventoryList& list = ref->mBase->mInventory; - MWWorld::ContainerStore& store = getContainerStore(ptr); - - // setting ownership not needed, since taking items from a container inherits the - // container's owner automatically - store.restock(list, ptr, ""); - } - void Container::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { if (!model.empty()) { @@ -228,12 +214,12 @@ namespace MWClass return !name.empty() ? name : ref->mBase->mId; } - MWWorld::ContainerStore& Container::getContainerStore (const MWWorld::Ptr& ptr) - const + MWWorld::ContainerStore& Container::getContainerStore (const MWWorld::Ptr& ptr) const { ensureCustomData (ptr); - - return ptr.getRefData().getCustomData()->asContainerCustomData().mContainerStore; + auto& data = ptr.getRefData().getCustomData()->asContainerCustomData(); + data.mStore.mPtr = ptr; + return data.mStore; } std::string Container::getScript (const MWWorld::ConstPtr& ptr) const @@ -253,8 +239,7 @@ namespace MWClass bool Container::hasToolTip (const MWWorld::ConstPtr& ptr) const { if (const MWWorld::CustomData* data = ptr.getRefData().getCustomData()) - return !canBeHarvested(ptr) || data->asContainerCustomData().mContainerStore.hasVisibleItems(); - + return !canBeHarvested(ptr) || data->asContainerCustomData().mStore.hasVisibleItems(); return true; } @@ -317,28 +302,20 @@ namespace MWClass if (!state.mHasCustomState) return; - if (!ptr.getRefData().getCustomData()) - { - // Create a CustomData, but don't fill it from ESM records (not needed) - std::unique_ptr data (new ContainerCustomData); - ptr.getRefData().setCustomData (data.release()); - } - - ContainerCustomData& customData = ptr.getRefData().getCustomData()->asContainerCustomData(); const ESM::ContainerState& containerState = state.asContainerState(); - customData.mContainerStore.readState (containerState.mInventory); + ptr.getRefData().setCustomData(std::make_unique(containerState.mInventory).release()); } void Container::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const { - if (!ptr.getRefData().getCustomData()) + const ContainerCustomData& customData = ptr.getRefData().getCustomData()->asContainerCustomData(); + if (!ptr.getRefData().getCustomData() || !customData.mStore.isResolved()) { state.mHasCustomState = false; return; } - const ContainerCustomData& customData = ptr.getRefData().getCustomData()->asContainerCustomData(); ESM::ContainerState& containerState = state.asContainerState(); - customData.mContainerStore.writeState (containerState.mInventory); + customData.mStore.writeState (containerState.mInventory); } } diff --git a/apps/openmw/mwclass/container.hpp b/apps/openmw/mwclass/container.hpp index b84c5787b..0ee549f81 100644 --- a/apps/openmw/mwclass/container.hpp +++ b/apps/openmw/mwclass/container.hpp @@ -2,9 +2,32 @@ #define GAME_MWCLASS_CONTAINER_H #include "../mwworld/class.hpp" +#include "../mwworld/containerstore.hpp" +#include "../mwworld/customdata.hpp" + +namespace ESM +{ + struct Container; + struct InventoryState; +} namespace MWClass { + class ContainerCustomData : public MWWorld::CustomData + { + MWWorld::ContainerStore mStore; + public: + ContainerCustomData(const ESM::Container& container, MWWorld::CellStore* cell); + ContainerCustomData(const ESM::InventoryState& inventory); + + virtual MWWorld::CustomData *clone() const; + + virtual ContainerCustomData& asContainerCustomData(); + virtual const ContainerCustomData& asContainerCustomData() const; + + friend class Container; + }; + class Container : public MWWorld::Class { void ensureCustomData (const MWWorld::Ptr& ptr) const; @@ -60,8 +83,6 @@ namespace MWClass virtual void respawn (const MWWorld::Ptr& ptr) const; - virtual void restock (const MWWorld::Ptr &ptr) const; - virtual std::string getModel(const MWWorld::ConstPtr &ptr) const; virtual bool useAnim() const; diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 5c5524a12..8e67498a1 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -842,14 +842,6 @@ namespace MWClass } } - void Creature::restock(const MWWorld::Ptr& ptr) const - { - MWWorld::LiveCellRef *ref = ptr.get(); - const ESM::InventoryList& list = ref->mBase->mInventory; - MWWorld::ContainerStore& store = getContainerStore(ptr); - store.restock(list, ptr, ptr.getCellRef().getRefId()); - } - int Creature::getBaseFightRating(const MWWorld::ConstPtr &ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index ca991052b..21d25633a 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -123,8 +123,6 @@ namespace MWClass virtual void respawn (const MWWorld::Ptr& ptr) const; - virtual void restock (const MWWorld::Ptr &ptr) const; - virtual int getBaseFightRating(const MWWorld::ConstPtr &ptr) const; virtual void adjustScale(const MWWorld::ConstPtr& ptr, osg::Vec3f& scale, bool rendering) const; diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index f4a711432..0187f2727 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1401,14 +1401,6 @@ namespace MWClass } } - void Npc::restock(const MWWorld::Ptr& ptr) const - { - MWWorld::LiveCellRef *ref = ptr.get(); - const ESM::InventoryList& list = ref->mBase->mInventory; - MWWorld::ContainerStore& store = getContainerStore(ptr); - store.restock(list, ptr, ptr.getCellRef().getRefId()); - } - int Npc::getBaseFightRating (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index 1ed4e8cae..43340b8d7 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -158,8 +158,6 @@ namespace MWClass virtual void respawn (const MWWorld::Ptr& ptr) const; - virtual void restock (const MWWorld::Ptr& ptr) const; - virtual int getBaseFightRating (const MWWorld::ConstPtr& ptr) const; virtual std::string getPrimaryFaction(const MWWorld::ConstPtr &ptr) const; diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index a68fddca1..810a369d8 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -168,6 +168,7 @@ namespace MWGui if (!mPtr.isEmpty()) MWBase::Environment::get().getMechanicsManager()->onClose(mPtr); + resetReference(); } void ContainerWindow::onCloseButtonClicked(MyGUI::Widget* _sender) diff --git a/apps/openmw/mwgui/containeritemmodel.cpp b/apps/openmw/mwgui/containeritemmodel.cpp index 0cfa6ebf5..31439f349 100644 --- a/apps/openmw/mwgui/containeritemmodel.cpp +++ b/apps/openmw/mwgui/containeritemmodel.cpp @@ -39,22 +39,29 @@ namespace namespace MWGui { - ContainerItemModel::ContainerItemModel(const std::vector& itemSources, const std::vector& worldItems) - : mItemSources(itemSources) - , mWorldItems(worldItems) + : mWorldItems(worldItems) + , mTrading(true) { - assert (!mItemSources.empty()); + assert (!itemSources.empty()); + // Tie resolution lifetimes to the ItemModel + mItemSources.reserve(itemSources.size()); + for(const MWWorld::Ptr& source : itemSources) + { + MWWorld::ContainerStore& store = source.getClass().getContainerStore(source); + mItemSources.push_back(std::make_pair(source, store.resolveTemporarily())); + } } -ContainerItemModel::ContainerItemModel (const MWWorld::Ptr& source) +ContainerItemModel::ContainerItemModel (const MWWorld::Ptr& source) : mTrading(false) { - mItemSources.push_back(source); + MWWorld::ContainerStore& store = source.getClass().getContainerStore(source); + mItemSources.push_back(std::make_pair(source, store.resolveTemporarily())); } bool ContainerItemModel::allowedToUseItems() const { - if (mItemSources.size() == 0) + if (mItemSources.empty()) return true; MWWorld::Ptr ptr = MWMechanics::getPlayer(); @@ -62,7 +69,7 @@ bool ContainerItemModel::allowedToUseItems() const // Check if the player is allowed to use items from opened container MWBase::MechanicsManager* mm = MWBase::Environment::get().getMechanicsManager(); - return mm->isAllowedToUse(ptr, mItemSources[0], victim); + return mm->isAllowedToUse(ptr, mItemSources[0].first, victim); } ItemStack ContainerItemModel::getItem (ModelIndex index) @@ -93,25 +100,31 @@ ItemModel::ModelIndex ContainerItemModel::getIndex (ItemStack item) MWWorld::Ptr ContainerItemModel::copyItem (const ItemStack& item, size_t count, bool allowAutoEquip) { - const MWWorld::Ptr& source = mItemSources[mItemSources.size()-1]; - if (item.mBase.getContainerStore() == &source.getClass().getContainerStore(source)) + auto& source = mItemSources[0]; + MWWorld::ContainerStore& store = source.first.getClass().getContainerStore(source.first); + if (item.mBase.getContainerStore() == &store) throw std::runtime_error("Item to copy needs to be from a different container!"); - return *source.getClass().getContainerStore(source).add(item.mBase, count, source, allowAutoEquip); + return *store.add(item.mBase, count, source.first, allowAutoEquip); } void ContainerItemModel::removeItem (const ItemStack& item, size_t count) { int toRemove = count; - for (MWWorld::Ptr& source : mItemSources) + for (auto& source : mItemSources) { - MWWorld::ContainerStore& store = source.getClass().getContainerStore(source); + MWWorld::ContainerStore& store = source.first.getClass().getContainerStore(source.first); for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { if (stacks(*it, item.mBase)) { - toRemove -= store.remove(*it, toRemove, source); + int quantity = it->mRef->mData.getCount(false); + // If this is a restocking quantity, just don't remove it + if(quantity < 0 && mTrading) + toRemove += quantity; + else + toRemove -= store.remove(*it, toRemove, source.first); if (toRemove <= 0) return; } @@ -138,9 +151,9 @@ void ContainerItemModel::removeItem (const ItemStack& item, size_t count) void ContainerItemModel::update() { mItems.clear(); - for (MWWorld::Ptr& source : mItemSources) + for (auto& source : mItemSources) { - MWWorld::ContainerStore& store = source.getClass().getContainerStore(source); + MWWorld::ContainerStore& store = source.first.getClass().getContainerStore(source.first); for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { @@ -194,7 +207,7 @@ bool ContainerItemModel::onDropItem(const MWWorld::Ptr &item, int count) if (mItemSources.empty()) return false; - MWWorld::Ptr target = mItemSources[0]; + MWWorld::Ptr target = mItemSources[0].first; if (target.getTypeName() != typeid(ESM::Container).name()) return true; @@ -224,7 +237,7 @@ bool ContainerItemModel::onTakeItem(const MWWorld::Ptr &item, int count) if (mItemSources.empty()) return false; - MWWorld::Ptr target = mItemSources[0]; + MWWorld::Ptr target = mItemSources[0].first; // Looting a dead corpse is considered OK if (target.getClass().isActor() && target.getClass().getCreatureStats(target).isDead()) diff --git a/apps/openmw/mwgui/containeritemmodel.hpp b/apps/openmw/mwgui/containeritemmodel.hpp index 806cc0a73..d09bd7def 100644 --- a/apps/openmw/mwgui/containeritemmodel.hpp +++ b/apps/openmw/mwgui/containeritemmodel.hpp @@ -1,8 +1,13 @@ #ifndef MWGUI_CONTAINER_ITEM_MODEL_H #define MWGUI_CONTAINER_ITEM_MODEL_H +#include +#include + #include "itemmodel.hpp" +#include "../mwworld/containerstore.hpp" + namespace MWGui { @@ -32,9 +37,9 @@ namespace MWGui virtual void update(); private: - std::vector mItemSources; + std::vector> mItemSources; std::vector mWorldItems; - + const bool mTrading; std::vector mItems; }; diff --git a/apps/openmw/mwgui/tradeitemmodel.cpp b/apps/openmw/mwgui/tradeitemmodel.cpp index b1bab32dc..f5144ba81 100644 --- a/apps/openmw/mwgui/tradeitemmodel.cpp +++ b/apps/openmw/mwgui/tradeitemmodel.cpp @@ -119,7 +119,7 @@ namespace MWGui mBorrowedToUs.clear(); } - std::vector TradeItemModel::getItemsBorrowedToUs() + const std::vector TradeItemModel::getItemsBorrowedToUs() const { return mBorrowedToUs; } diff --git a/apps/openmw/mwgui/tradeitemmodel.hpp b/apps/openmw/mwgui/tradeitemmodel.hpp index cdb949c49..e349c225a 100644 --- a/apps/openmw/mwgui/tradeitemmodel.hpp +++ b/apps/openmw/mwgui/tradeitemmodel.hpp @@ -40,7 +40,7 @@ namespace MWGui /// and removing weight for items we've lent to someone else. void adjustEncumbrance (float& encumbrance); - std::vector getItemsBorrowedToUs(); + const std::vector getItemsBorrowedToUs() const; private: void borrowImpl(const ItemStack& item, std::vector& out); diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index 672ccbd06..e7dbd4447 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -99,20 +99,6 @@ namespace MWGui setCoord(400, 0, 400, 300); } - void TradeWindow::restock() - { - // Restock items on the actor inventory - mPtr.getClass().restock(mPtr); - - // Also restock any containers owned by this merchant, which are also available to buy in the trade window - std::vector itemSources; - MWBase::Environment::get().getWorld()->getContainersOwnedBy(mPtr, itemSources); - for (MWWorld::Ptr& source : itemSources) - { - source.getClass().restock(source); - } - } - void TradeWindow::setPtr(const MWWorld::Ptr& actor) { mPtr = actor; @@ -121,10 +107,10 @@ namespace MWGui mCurrentMerchantOffer = 0; std::vector itemSources; + // Important: actor goes first, so purchased items come out of the actor's pocket first + itemSources.push_back(actor); MWBase::Environment::get().getWorld()->getContainersOwnedBy(actor, itemSources); - // Important: actor goes last, so that items purchased by the merchant go into his inventory - itemSources.push_back(actor); std::vector worldItems; MWBase::Environment::get().getWorld()->getItemsOwnedBy(actor, worldItems); @@ -281,8 +267,8 @@ namespace MWGui MWBase::Environment::get().getWorld()->getStore().get(); // were there any items traded at all? - std::vector playerBought = playerItemModel->getItemsBorrowedToUs(); - std::vector merchantBought = mTradeModel->getItemsBorrowedToUs(); + const std::vector& playerBought = playerItemModel->getItemsBorrowedToUs(); + const std::vector& merchantBought = mTradeModel->getItemsBorrowedToUs(); if (playerBought.empty() && merchantBought.empty()) { // user notification @@ -313,7 +299,7 @@ namespace MWGui } // check if the player is attempting to sell back an item stolen from this actor - for (ItemStack& itemStack : merchantBought) + for (const ItemStack& itemStack : merchantBought) { if (MWBase::Environment::get().getMechanicsManager()->isItemStolenFrom(itemStack.mBase.getCellRef().getRefId(), mPtr)) { @@ -363,8 +349,6 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->playSound("Item Gold Up"); MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Barter); - - restock(); } void TradeWindow::onAccept(MyGUI::EditBox *sender) @@ -478,7 +462,7 @@ namespace MWGui // connected to buying and selling the same item. // This value has been determined by researching the limitations of the vanilla formula // and may not be sufficient if getBarterOffer behavior has been changed. - std::vector playerBorrowed = playerTradeModel->getItemsBorrowedToUs(); + const std::vector& playerBorrowed = playerTradeModel->getItemsBorrowedToUs(); for (const ItemStack& itemStack : playerBorrowed) { const int basePrice = getEffectiveValue(itemStack.mBase, itemStack.mCount); @@ -487,7 +471,7 @@ namespace MWGui merchantOffer -= std::max(cap, buyingPrice); } - std::vector merchantBorrowed = mTradeModel->getItemsBorrowedToUs(); + const std::vector& merchantBorrowed = mTradeModel->getItemsBorrowedToUs(); for (const ItemStack& itemStack : merchantBorrowed) { const int basePrice = getEffectiveValue(itemStack.mBase, itemStack.mCount); @@ -532,4 +516,9 @@ namespace MWGui mTradeModel = nullptr; mSortModel = nullptr; } + + void TradeWindow::onClose() + { + resetReference(); + } } diff --git a/apps/openmw/mwgui/tradewindow.hpp b/apps/openmw/mwgui/tradewindow.hpp index 0730df04f..b211c6d09 100644 --- a/apps/openmw/mwgui/tradewindow.hpp +++ b/apps/openmw/mwgui/tradewindow.hpp @@ -29,6 +29,7 @@ namespace MWGui void setPtr(const MWWorld::Ptr& actor); + virtual void onClose() override; void onFrame(float dt); void clear() { resetReference(); } @@ -111,8 +112,6 @@ namespace MWGui virtual void onReferenceUnavailable(); int getMerchantGold(); - - void restock(); }; } diff --git a/apps/openmw/mwmechanics/levelledlist.hpp b/apps/openmw/mwmechanics/levelledlist.hpp index f716f068d..c8368101a 100644 --- a/apps/openmw/mwmechanics/levelledlist.hpp +++ b/apps/openmw/mwmechanics/levelledlist.hpp @@ -19,14 +19,14 @@ namespace MWMechanics { /// @return ID of resulting item, or empty if none - inline std::string getLevelledItem (const ESM::LevelledListBase* levItem, bool creature) + inline std::string getLevelledItem (const ESM::LevelledListBase* levItem, bool creature, Misc::Rng::Seed& seed = Misc::Rng::getSeed()) { const std::vector& items = levItem->mList; const MWWorld::Ptr& player = getPlayer(); int playerLevel = player.getClass().getCreatureStats(player).getLevel(); - if (Misc::Rng::roll0to99() < levItem->mChanceNone) + if (Misc::Rng::roll0to99(seed) < levItem->mChanceNone) return std::string(); std::vector candidates; @@ -55,7 +55,7 @@ namespace MWMechanics } if (candidates.empty()) return std::string(); - std::string item = candidates[Misc::Rng::rollDice(candidates.size())]; + std::string item = candidates[Misc::Rng::rollDice(candidates.size(), seed)]; // Vanilla doesn't fail on nonexistent items in levelled lists if (!MWBase::Environment::get().getWorld()->getStore().find(Misc::StringUtils::lowerCase(item))) @@ -74,9 +74,9 @@ namespace MWMechanics else { if (ref.getPtr().getTypeName() == typeid(ESM::ItemLevList).name()) - return getLevelledItem(ref.getPtr().get()->mBase, false); + return getLevelledItem(ref.getPtr().get()->mBase, false, seed); else - return getLevelledItem(ref.getPtr().get()->mBase, true); + return getLevelledItem(ref.getPtr().get()->mBase, true, seed); } } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 466a3bcc8..872a66799 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1045,6 +1045,7 @@ namespace MWMechanics void MechanicsManager::confiscateStolenItems(const MWWorld::Ptr &player, const MWWorld::Ptr &targetContainer) { MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); + MWWorld::ContainerStore& containerStore = targetContainer.getClass().getContainerStore(targetContainer); for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { StolenItemsMap::iterator stolenIt = mStolenItems.find(Misc::StringUtils::lowerCase(it->getCellRef().getRefId())); @@ -1065,7 +1066,7 @@ namespace MWMechanics int toMove = it->getRefData().getCount() - itemCount; - targetContainer.getClass().getContainerStore(targetContainer).add(*it, toMove, targetContainer); + containerStore.add(*it, toMove, targetContainer); store.remove(*it, toMove, player); } // TODO: unhardcode the locklevel diff --git a/apps/openmw/mwworld/actionharvest.cpp b/apps/openmw/mwworld/actionharvest.cpp index ff35938b2..c9468c715 100644 --- a/apps/openmw/mwworld/actionharvest.cpp +++ b/apps/openmw/mwworld/actionharvest.cpp @@ -29,6 +29,7 @@ namespace MWWorld MWWorld::Ptr target = getTarget(); MWWorld::ContainerStore& store = target.getClass().getContainerStore (target); + store.resolve(); MWWorld::ContainerStore& actorStore = actor.getClass().getContainerStore(actor); std::map takenMap; for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 2cda83e17..14b96b27c 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -1030,7 +1030,8 @@ namespace MWWorld for (CellRefList::List::iterator it (mContainers.mList.begin()); it!=mContainers.mList.end(); ++it) { Ptr ptr = getCurrentPtr(&*it); - if (!ptr.isEmpty() && ptr.getRefData().getCustomData() != nullptr && ptr.getRefData().getCount() > 0) + if (!ptr.isEmpty() && ptr.getRefData().getCustomData() != nullptr && ptr.getRefData().getCount() > 0 + && ptr.getClass().getContainerStore(ptr).isResolved()) { ptr.getClass().getContainerStore(ptr).rechargeItems(duration); } diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 445f2e986..1b3d4029e 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -353,8 +353,6 @@ namespace MWWorld virtual void respawn (const MWWorld::Ptr& ptr) const {} - virtual void restock (const MWWorld::Ptr& ptr) const {} - /// Returns sound id virtual std::string getSound(const MWWorld::ConstPtr& ptr) const; diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index 6337ca440..5ecb3dd3a 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -23,6 +23,21 @@ namespace { + void addScripts(MWWorld::ContainerStore& store, MWWorld::CellStore* cell) + { + auto& scripts = MWBase::Environment::get().getWorld()->getLocalScripts(); + for(const MWWorld::Ptr& ptr : store) + { + const std::string& 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) { @@ -44,6 +59,7 @@ namespace MWWorld::Ptr searchId (MWWorld::CellRefList& list, const std::string& id, MWWorld::ContainerStore *store) { + store->resolve(); std::string id2 = Misc::StringUtils::lowerCase (id); for (typename MWWorld::CellRefList::List::iterator iter (list.mList.begin()); @@ -61,6 +77,18 @@ namespace } } +MWWorld::ResolutionListener::~ResolutionListener() +{ + if(!mStore.mModified && mStore.mResolved && !mStore.mPtr.isEmpty()) + { + for(const MWWorld::Ptr& ptr : mStore) + ptr.getRefData().setCount(0); + mStore.fillNonRandom(mStore.mPtr.get()->mBase->mInventory, "", mStore.mSeed); + addScripts(mStore, mStore.mPtr.mCell); + mStore.mResolved = false; + } +} + template MWWorld::ContainerStoreIterator MWWorld::ContainerStore::getState (CellRefList& collection, const ESM::ObjectState& state) @@ -119,7 +147,11 @@ MWWorld::ContainerStore::ContainerStore() : mListener(nullptr) , mRechargingItemsUpToDate(false) , mCachedWeight (0) - , mWeightUpToDate (false) {} + , mWeightUpToDate (false) + , mModified(false) + , mResolved(false) + , mSeed() + , mPtr() {} MWWorld::ContainerStore::~ContainerStore() {} @@ -153,22 +185,12 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::end() return ContainerStoreIterator (this); } -int MWWorld::ContainerStore::count(const std::string &id) +int MWWorld::ContainerStore::count(const std::string &id) const { int total=0; - for (MWWorld::ContainerStoreIterator iter (begin()); iter!=end(); ++iter) - if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), id)) - total += iter->getRefData().getCount(); - return total; -} - -int MWWorld::ContainerStore::restockCount(const std::string &id) -{ - int total=0; - for (MWWorld::ContainerStoreIterator iter (begin()); iter!=end(); ++iter) - if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), id)) - if (iter->getCellRef().getSoul().empty()) - total += iter->getRefData().getCount(); + for (const auto& iter : *this) + if (Misc::StringUtils::ciEqual(iter.getCellRef().getRefId(), id)) + total += iter.getRefData().getCount(); return total; } @@ -185,9 +207,10 @@ void MWWorld::ContainerStore::setContListener(MWWorld::ContainerStoreListener* l MWWorld::ContainerStoreIterator MWWorld::ContainerStore::unstack(const Ptr &ptr, const Ptr& container, int count) { + resolve(); if (ptr.getRefData().getCount() <= count) return end(); - MWWorld::ContainerStoreIterator it = addNewStack(ptr, ptr.getRefData().getCount()-count); + MWWorld::ContainerStoreIterator it = addNewStack(ptr, subtractItems(ptr.getRefData().getCount(false), count)); const std::string script = it->getClass().getScript(*it); if (!script.empty()) MWBase::Environment::get().getWorld()->getLocalScripts().add(script, *it); @@ -199,6 +222,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::unstack(const Ptr &ptr, MWWorld::ContainerStoreIterator MWWorld::ContainerStore::restack(const MWWorld::Ptr& item) { + resolve(); MWWorld::ContainerStoreIterator retval = end(); for (MWWorld::ContainerStoreIterator iter (begin()); iter != end(); ++iter) { @@ -216,7 +240,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::restack(const MWWorld:: { if (stacks(*iter, item)) { - iter->getRefData().setCount(iter->getRefData().getCount() + item.getRefData().getCount()); + iter->getRefData().setCount(addItems(iter->getRefData().getCount(false), item.getRefData().getCount(false))); item.getRefData().setCount(0); retval = iter; break; @@ -328,8 +352,10 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr return it; } -MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp (const Ptr& ptr, int count) +MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp (const Ptr& ptr, int count, bool markModified) { + if(markModified) + resolve(); int type = getType(ptr); const MWWorld::ESMStore &esmStore = @@ -345,7 +371,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp (const Ptr& ptr, { if (Misc::StringUtils::ciEqual((*iter).getCellRef().getRefId(), MWWorld::ContainerStore::sGoldId)) { - iter->getRefData().setCount(iter->getRefData().getCount() + realCount); + iter->getRefData().setCount(addItems(iter->getRefData().getCount(false), realCount)); flagAsModified(); return iter; } @@ -361,7 +387,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp (const Ptr& ptr, if (stacks(*iter, ptr)) { // stack - iter->getRefData().setCount( iter->getRefData().getCount() + count ); + iter->getRefData().setCount(addItems(iter->getRefData().getCount(false), count)); flagAsModified(); return iter; @@ -439,6 +465,7 @@ void MWWorld::ContainerStore::updateRechargingItems() int MWWorld::ContainerStore::remove(const std::string& itemId, int count, const Ptr& actor) { + resolve(); int toRemove = count; for (ContainerStoreIterator iter(begin()); iter != end() && toRemove > 0; ++iter) @@ -465,6 +492,7 @@ bool MWWorld::ContainerStore::hasVisibleItems() const int MWWorld::ContainerStore::remove(const Ptr& item, int count, const Ptr& actor) { assert(this == item.getContainerStore()); + resolve(); int toRemove = count; RefData& itemRef = item.getRefData(); @@ -476,7 +504,7 @@ int MWWorld::ContainerStore::remove(const Ptr& item, int count, const Ptr& actor } else { - itemRef.setCount(itemRef.getCount() - toRemove); + itemRef.setCount(subtractItems(itemRef.getCount(false), toRemove)); toRemove = 0; } @@ -490,20 +518,33 @@ int MWWorld::ContainerStore::remove(const Ptr& item, int count, const Ptr& actor return count - toRemove; } -void MWWorld::ContainerStore::fill (const ESM::InventoryList& items, const std::string& owner) +void MWWorld::ContainerStore::fill (const ESM::InventoryList& items, const std::string& owner, Misc::Rng::Seed& seed) { - for (std::vector::const_iterator iter (items.mList.begin()); iter!=items.mList.end(); - ++iter) + for (const ESM::ContItem& iter : items.mList) { - std::string id = Misc::StringUtils::lowerCase(iter->mItem); - addInitialItem(id, owner, iter->mCount); + std::string id = Misc::StringUtils::lowerCase(iter.mItem); + addInitialItem(id, owner, iter.mCount, &seed); } flagAsModified(); + mResolved = true; } -void MWWorld::ContainerStore::addInitialItem (const std::string& id, const std::string& owner, - int count, bool topLevel, const std::string& levItem) +void MWWorld::ContainerStore::fillNonRandom (const ESM::InventoryList& items, const std::string& owner, unsigned int seed) +{ + mSeed = seed; + for (const ESM::ContItem& iter : items.mList) + { + std::string id = Misc::StringUtils::lowerCase(iter.mItem); + addInitialItem(id, owner, iter.mCount, nullptr); + } + + flagAsModified(); + mResolved = false; +} + +void MWWorld::ContainerStore::addInitialItem (const std::string& id, const std::string& owner, int count, + Misc::Rng::Seed* seed, bool topLevel, const std::string& levItem) { if (count == 0) return; //Don't restock with nothing. try @@ -511,13 +552,13 @@ void MWWorld::ContainerStore::addInitialItem (const std::string& id, const std:: ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), id, count); if (ref.getPtr().getClass().getScript(ref.getPtr()).empty()) { - addInitialItemImp(ref.getPtr(), owner, count, topLevel, levItem); + addInitialItemImp(ref.getPtr(), owner, count, seed, topLevel, levItem); } else { // Adding just one item per time to make sure there isn't a stack of scripted items - for (int i = 0; i < abs(count); i++) - addInitialItemImp(ref.getPtr(), owner, count < 0 ? -1 : 1, topLevel, levItem); + for (int i = 0; i < std::abs(count); i++) + addInitialItemImp(ref.getPtr(), owner, count < 0 ? -1 : 1, seed, topLevel, levItem); } } catch (const std::exception& e) @@ -526,137 +567,43 @@ void MWWorld::ContainerStore::addInitialItem (const std::string& id, const std:: } } -void MWWorld::ContainerStore::addInitialItemImp(const MWWorld::Ptr& ptr, const std::string& owner, - int count, bool topLevel, const std::string& levItem) +void MWWorld::ContainerStore::addInitialItemImp(const MWWorld::Ptr& ptr, const std::string& owner, int count, + Misc::Rng::Seed* seed, bool topLevel, const std::string& levItem) { if (ptr.getTypeName()==typeid (ESM::ItemLevList).name()) { + if(!seed) + return; const ESM::ItemLevList* levItemList = ptr.get()->mBase; if (topLevel && std::abs(count) > 1 && levItemList->mFlags & ESM::ItemLevList::Each) { for (int i=0; i 0 ? 1 : -1, true, levItemList->mId); + addInitialItem(ptr.getCellRef().getRefId(), owner, count > 0 ? 1 : -1, seed, true, levItemList->mId); return; } else { - std::string itemId = MWMechanics::getLevelledItem(ptr.get()->mBase, false); + std::string itemId = MWMechanics::getLevelledItem(ptr.get()->mBase, false, *seed); if (itemId.empty()) return; - addInitialItem(itemId, owner, count, false, levItemList->mId); + addInitialItem(itemId, owner, count, seed, false, levItemList->mId); } } else { - // A negative count indicates restocking items - // For a restocking levelled item, remember what we spawned so we can delete it later when the merchant restocks - if (!levItem.empty() && count < 0) - { - //If there is no item in map, insert it - std::map, int>::iterator itemInMap = - mLevelledItemMap.insert(std::make_pair(std::make_pair(ptr.getCellRef().getRefId(), levItem), 0)).first; - //Update spawned count - itemInMap->second += std::abs(count); - } - count = std::abs(count); - ptr.getCellRef().setOwner(owner); - addImp (ptr, count); + addImp (ptr, count, false); } } -void MWWorld::ContainerStore::restock (const ESM::InventoryList& items, const MWWorld::Ptr& ptr, const std::string& owner) -{ - //allowedForReplace - Holds information about how many items from the list were not sold; - // Hence, tells us how many items we don't need to restock. - //allowedForReplace[list] <- How many items we should generate(how many of these were sold) - std::map allowedForReplace; - - //Check which lists need restocking: - for (std::map, int>::iterator it = mLevelledItemMap.begin(); it != mLevelledItemMap.end();) - { - int spawnedCount = it->second; //How many items should be in shop originally - int itemCount = restockCount(it->first.first); //How many items are there in shop now - //If something was not sold - if(itemCount >= spawnedCount) - { - const std::string& parent = it->first.second; - // Security check for old saves: - //If item is imported from old save(doesn't have an parent) and wasn't sold - if(parent == "") - { - //Remove it, from shop, - remove(it->first.first, itemCount, ptr);//ptr is the NPC - //And remove it from map, so that when we restock, the new item will have proper parent. - mLevelledItemMap.erase(it++); - continue; - } - //Create the entry if it does not exist yet - std::map::iterator listInMap = allowedForReplace.insert( - std::make_pair(it->first.second, 0)).first; - //And signal that we don't need to restock item from this list - listInMap->second += std::abs(itemCount); - } - //If every of the item was sold - else if (itemCount == 0) - { - mLevelledItemMap.erase(it++); - continue; - } - //If some was sold, but some remain - else - { - //Create entry if it does not exist yet - std::map::iterator listInMap = allowedForReplace.insert( - std::make_pair(it->first.second, 0)).first; - //And signal that we don't need to restock all items from this list - listInMap->second += std::abs(itemCount); - //And update itemCount so we don't mistake it next time. - it->second = itemCount; - } - ++it; - } - - //Restock: - //For every item that NPC could have - for (std::vector::const_iterator it = items.mList.begin(); it != items.mList.end(); ++it) - { - //If he shouldn't have it restocked, don't restock it. - if (it->mCount >= 0) - continue; - - std::string itemOrList = Misc::StringUtils::lowerCase(it->mItem); - - //If it's levelled list, restock if there's need to do so. - if (MWBase::Environment::get().getWorld()->getStore().get().search(it->mItem)) - { - std::map::iterator listInMap = allowedForReplace.find(itemOrList); - - int restockNum = std::abs(it->mCount); - //If we know we must restock less, take it into account - if(listInMap != allowedForReplace.end()) - restockNum -= std::min(restockNum, listInMap->second); - //restock - addInitialItem(itemOrList, owner, -restockNum, true); - } - else - { - //Restocking static item - just restock to the max count - int currentCount = restockCount(itemOrList); - if (currentCount < std::abs(it->mCount)) - addInitialItem(itemOrList, owner, -(std::abs(it->mCount) - currentCount), true); - } - } - flagAsModified(); -} - void MWWorld::ContainerStore::clear() { for (ContainerStoreIterator iter (begin()); iter!=end(); ++iter) iter->getRefData().setCount (0); flagAsModified(); + mModified = true; } void MWWorld::ContainerStore::flagAsModified() @@ -665,6 +612,45 @@ void MWWorld::ContainerStore::flagAsModified() mRechargingItemsUpToDate = false; } +bool MWWorld::ContainerStore::isResolved() const +{ + return mResolved; +} + +void MWWorld::ContainerStore::resolve() +{ + if(!mResolved && !mPtr.isEmpty()) + { + for(const MWWorld::Ptr& ptr : *this) + ptr.getRefData().setCount(0); + Misc::Rng::Seed seed{mSeed}; + fill(mPtr.get()->mBase->mInventory, "", seed); + 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 MWWorld::Ptr& ptr : *this) + ptr.getRefData().setCount(0); + Misc::Rng::Seed seed{mSeed}; + fill(mPtr.get()->mBase->mInventory, "", seed); + addScripts(*this, mPtr.mCell); + } + return {listener}; +} + float MWWorld::ContainerStore::getWeight() const { if (!mWeightUpToDate) @@ -761,6 +747,7 @@ MWWorld::Ptr MWWorld::ContainerStore::findReplacement(const std::string& id) MWWorld::Ptr MWWorld::ContainerStore::search (const std::string& id) { + resolve(); { Ptr ptr = searchId (potions, id, this); if (!ptr.isEmpty()) @@ -836,6 +823,22 @@ MWWorld::Ptr MWWorld::ContainerStore::search (const std::string& id) 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(); @@ -853,13 +856,13 @@ void MWWorld::ContainerStore::writeState (ESM::InventoryState& state) const storeStates (repairs, state, index); storeStates (weapons, state, index, true); storeStates (lights, state, index, true); - - state.mLevelledItemMap = mLevelledItemMap; } void MWWorld::ContainerStore::readState (const ESM::InventoryState& inventory) { clear(); + mModified = true; + mResolved = true; int index = 0; for (std::vector::const_iterator @@ -893,9 +896,6 @@ void MWWorld::ContainerStore::readState (const ESM::InventoryState& inventory) break; } } - - - mLevelledItemMap = inventory.mLevelledItemMap; } template diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index f2858c5aa..c89e855f9 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -18,6 +19,8 @@ #include #include +#include + #include "ptr.hpp" #include "cellreflist.hpp" @@ -27,6 +30,11 @@ namespace ESM struct InventoryState; } +namespace MWClass +{ + class Container; +} + namespace MWWorld { class ContainerStore; @@ -37,6 +45,21 @@ namespace MWWorld typedef ContainerStoreIteratorBase ContainerStoreIterator; typedef ContainerStoreIteratorBase ConstContainerStoreIterator; + class ResolutionListener + { + ContainerStore& mStore; + public: + ResolutionListener(ContainerStore& store) : mStore(store) {} + ~ResolutionListener(); + }; + + class ResolutionHandle + { + std::shared_ptr mListener; + public: + ResolutionHandle(std::shared_ptr listener) : mListener(listener) {} + ResolutionHandle() {} + }; class ContainerStoreListener { @@ -93,15 +116,18 @@ namespace MWWorld MWWorld::CellRefList repairs; MWWorld::CellRefList weapons; - std::map, int> mLevelledItemMap; - ///< Stores result of levelled item spawns. <(refId, spawningGroup), count> - /// This is used to restock levelled items(s) if the old item was sold. - mutable float mCachedWeight; mutable bool mWeightUpToDate; - ContainerStoreIterator addImp (const Ptr& ptr, int count); - void addInitialItem (const std::string& id, const std::string& owner, int count, bool topLevel=true, const std::string& levItem = ""); - void addInitialItemImp (const MWWorld::Ptr& ptr, const std::string& owner, int count, bool topLevel=true, const std::string& levItem = ""); + + bool mModified; + bool mResolved; + unsigned int mSeed; + MWWorld::Ptr mPtr; + std::weak_ptr mResolutionListener; + + ContainerStoreIterator addImp (const Ptr& ptr, int count, bool markModified = true); + void addInitialItem (const std::string& id, const std::string& owner, int count, Misc::Rng::Seed* seed, bool topLevel=true, const std::string& levItem = ""); + void addInitialItemImp (const MWWorld::Ptr& ptr, const std::string& owner, int count, Misc::Rng::Seed* seed, bool topLevel=true, const std::string& levItem = ""); template ContainerStoreIterator getState (CellRefList& collection, @@ -175,31 +201,31 @@ namespace MWWorld /// If a compatible stack is found, the item's count is added to that stack, then the original is deleted. /// @return If the item was stacked, return the stack, otherwise return the old (untouched) item. - int count (const std::string& id); + int count (const std::string& id) const; ///< @return How many items with refID \a id are in this container? - int restockCount (const std::string& id); - ///< Item count with restock adjustments (such as ignoring filled soul gems). - /// @return How many items with refID \a id are in this container? - ContainerStoreListener* getContListener() const; void setContListener(ContainerStoreListener* listener); - protected: ContainerStoreIterator addNewStack (const ConstPtr& ptr, int count); ///< Add the item to this container (do not try to stack it onto existing items) virtual void flagAsModified(); + /// + and - operations that can deal with negative stacks + /// Note that negativity is infectious + static int addItems(int count1, int count2); + static int subtractItems(int count1, int count2); public: virtual bool stacks (const ConstPtr& ptr1, const ConstPtr& ptr2) const; ///< @return true if the two specified objects can stack with each other - void fill (const ESM::InventoryList& items, const std::string& owner); + void fill (const ESM::InventoryList& items, const std::string& owner, Misc::Rng::Seed& seed = Misc::Rng::getSeed()); ///< Insert items into *this. - void restock (const ESM::InventoryList& items, const MWWorld::Ptr& ptr, const std::string& owner); + void fillNonRandom (const ESM::InventoryList& items, const std::string& owner, unsigned int seed); + ///< Insert items into *this, excluding leveled items virtual void clear(); ///< Empty container. @@ -220,8 +246,15 @@ namespace MWWorld virtual void readState (const ESM::InventoryState& state); + bool isResolved() const; + + void resolve(); + ResolutionHandle resolveTemporarily(); + friend class ContainerStoreIteratorBase; friend class ContainerStoreIteratorBase; + friend class ResolutionListener; + friend class MWClass::Container; }; diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index d67a22884..cf2b97ce7 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -86,8 +86,9 @@ void MWWorld::InventoryStore::readEquipmentState(const MWWorld::ContainerStoreIt // unstack if required if (!allowedSlots.second && iter->getRefData().getCount() > 1) { - MWWorld::ContainerStoreIterator newIter = addNewStack(*iter, 1); - iter->getRefData().setCount(iter->getRefData().getCount()-1); + int count = iter->getRefData().getCount(false); + MWWorld::ContainerStoreIterator newIter = addNewStack(*iter, count > 0 ? 1 : -1); + iter->getRefData().setCount(subtractItems(count, 1)); mSlots[slot] = newIter; } else @@ -850,8 +851,8 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipItemQuantity(con { if (stacks(*iter, item) && !isEquipped(*iter)) { - iter->getRefData().setCount(iter->getRefData().getCount() + count); - item.getRefData().setCount(item.getRefData().getCount() - count); + iter->getRefData().setCount(addItems(iter->getRefData().getCount(false), count)); + item.getRefData().setCount(subtractItems(item.getRefData().getCount(false), count)); return iter; } } diff --git a/apps/openmw/mwworld/localscripts.cpp b/apps/openmw/mwworld/localscripts.cpp index a727b4b3a..8a511030d 100644 --- a/apps/openmw/mwworld/localscripts.cpp +++ b/apps/openmw/mwworld/localscripts.cpp @@ -45,7 +45,7 @@ namespace // Ignore containers without generated content if (containerPtr.getTypeName() == typeid(ESM::Container).name() && containerPtr.getRefData().getCustomData() == nullptr) - return false; + return true; MWWorld::ContainerStore& container = containerPtr.getClass().getContainerStore(containerPtr); for(MWWorld::ContainerStoreIterator it = container.begin(); it != container.end(); ++it) diff --git a/apps/openmw/mwworld/refdata.cpp b/apps/openmw/mwworld/refdata.cpp index f6fa3556f..a5f8ef4b1 100644 --- a/apps/openmw/mwworld/refdata.cpp +++ b/apps/openmw/mwworld/refdata.cpp @@ -146,8 +146,10 @@ namespace MWWorld return mBaseNode; } - int RefData::getCount() const + int RefData::getCount(bool absolute) const { + if(absolute) + return std::abs(mCount); return mCount; } diff --git a/apps/openmw/mwworld/refdata.hpp b/apps/openmw/mwworld/refdata.hpp index 6a59d9797..738a6d53a 100644 --- a/apps/openmw/mwworld/refdata.hpp +++ b/apps/openmw/mwworld/refdata.hpp @@ -86,7 +86,7 @@ namespace MWWorld /// Set base node (can be a null pointer). void setBaseNode (SceneUtil::PositionAttitudeTransform* base); - int getCount() const; + int getCount(bool absolute = true) const; void setLocals (const ESM::Script& script); diff --git a/components/detournavigator/findrandompointaroundcircle.cpp b/components/detournavigator/findrandompointaroundcircle.cpp index 3888c59fe..263dba68e 100644 --- a/components/detournavigator/findrandompointaroundcircle.cpp +++ b/components/detournavigator/findrandompointaroundcircle.cpp @@ -36,7 +36,7 @@ namespace DetourNavigator dtPolyRef resultRef = 0; osg::Vec3f resultPosition; navMeshQuery.findRandomPointAroundCircle(startRef, start.ptr(), maxRadius, &queryFilter, - &Misc::Rng::rollProbability, &resultRef, resultPosition.ptr()); + []() { return Misc::Rng::rollProbability(); }, &resultRef, resultPosition.ptr()); if (resultRef == 0) return boost::optional(); diff --git a/components/esm/inventorystate.cpp b/components/esm/inventorystate.cpp index fe54762c5..2392d263f 100644 --- a/components/esm/inventorystate.cpp +++ b/components/esm/inventorystate.cpp @@ -3,6 +3,8 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + void ESM::InventoryState::load (ESMReader &esm) { // obsolete @@ -106,6 +108,19 @@ void ESM::InventoryState::load (ESMReader &esm) mSelectedEnchantItem = -1; esm.getHNOT(mSelectedEnchantItem, "SELE"); + + // Old saves had restocking levelled items in a special map + // This turns items from that map into negative quantities + for(const auto& entry : mLevelledItemMap) + { + const std::string& id = entry.first.first; + const int count = entry.second; + for(auto& item : mItems) + { + if(item.mCount == count && Misc::StringUtils::ciEqual(id, item.mRef.mRefID)) + item.mCount = -count; + } + } } void ESM::InventoryState::save (ESMWriter &esm) const diff --git a/components/esm/savedgame.cpp b/components/esm/savedgame.cpp index 0fc84e309..9edcb1a67 100644 --- a/components/esm/savedgame.cpp +++ b/components/esm/savedgame.cpp @@ -4,7 +4,7 @@ #include "esmwriter.hpp" unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE; -int ESM::SavedGame::sCurrentFormat = 14; +int ESM::SavedGame::sCurrentFormat = 15; void ESM::SavedGame::load (ESMReader &esm) { diff --git a/components/misc/rng.cpp b/components/misc/rng.cpp index 09279e85e..4189404b1 100644 --- a/components/misc/rng.cpp +++ b/components/misc/rng.cpp @@ -3,29 +3,44 @@ #include #include +namespace +{ + Misc::Rng::Seed sSeed; +} + namespace Misc { - std::mt19937 Rng::generator = std::mt19937(); + Rng::Seed::Seed() {} + + Rng::Seed::Seed(unsigned int seed) + { + mGenerator.seed(seed); + } + + Rng::Seed& Rng::getSeed() + { + return sSeed; + } void Rng::init(unsigned int seed) { - generator.seed(seed); + sSeed.mGenerator.seed(seed); } - float Rng::rollProbability() + float Rng::rollProbability(Seed& seed) { - return std::uniform_real_distribution(0, 1 - std::numeric_limits::epsilon())(generator); + return std::uniform_real_distribution(0, 1 - std::numeric_limits::epsilon())(sSeed.mGenerator); } - float Rng::rollClosedProbability() + float Rng::rollClosedProbability(Seed& seed) { - return std::uniform_real_distribution(0, 1)(generator); + return std::uniform_real_distribution(0, 1)(sSeed.mGenerator); } - int Rng::rollDice(int max) + int Rng::rollDice(int max, Seed& seed) { - return max > 0 ? std::uniform_int_distribution(0, max - 1)(generator) : 0; + return max > 0 ? std::uniform_int_distribution(0, max - 1)(sSeed.mGenerator) : 0; } unsigned int Rng::generateDefaultSeed() diff --git a/components/misc/rng.hpp b/components/misc/rng.hpp index 65a554cf2..8efca438d 100644 --- a/components/misc/rng.hpp +++ b/components/misc/rng.hpp @@ -13,24 +13,32 @@ namespace Misc class Rng { public: + class Seed + { + std::mt19937 mGenerator; + public: + Seed(); + Seed(const Seed&) = delete; + Seed(unsigned int seed); + friend class Rng; + }; - /// create a RNG - static std::mt19937 generator; + static Seed& getSeed(); /// seed the RNG static void init(unsigned int seed = generateDefaultSeed()); /// return value in range [0.0f, 1.0f) <- note open upper range. - static float rollProbability(); + static float rollProbability(Seed& seed = getSeed()); /// return value in range [0.0f, 1.0f] <- note closed upper range. - static float rollClosedProbability(); + static float rollClosedProbability(Seed& seed = getSeed()); /// return value in range [0, max) <- note open upper range. - static int rollDice(int max); + static int rollDice(int max, Seed& seed = getSeed()); /// return value in range [0, 99] - static int roll0to99() { return rollDice(100); } + static int roll0to99(Seed& seed = getSeed()) { return rollDice(100, seed); } /// returns default seed for RNG static unsigned int generateDefaultSeed();