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
pull/3001/head^2
Assumeru 4 years ago committed by GitHub
parent c99be77a32
commit 72549651e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -5,8 +5,11 @@
Bug #1952: Incorrect particle lighting Bug #1952: Incorrect particle lighting
Bug #2069: Fireflies in Fireflies invade Morrowind look wrong Bug #2069: Fireflies in Fireflies invade Morrowind look wrong
Bug #2311: Targeted scripts are not properly supported on non-unique RefIDs 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 #3676: NiParticleColorModifier isn't applied properly
Bug #3714: Savegame fails to load due to conflict between SpellState and MagicEffects 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 #4021: Attributes and skills are not stored as floats
Bug #4055: Local scripts don't inherit variables from their base record Bug #4055: Local scripts don't inherit variables from their base record
Bug #4623: Corprus implementation is incorrect Bug #4623: Corprus implementation is incorrect

@ -31,44 +31,41 @@
namespace MWClass namespace MWClass
{ {
class ContainerCustomData : public MWWorld::CustomData ContainerCustomData::ContainerCustomData(const ESM::Container& container, MWWorld::CellStore* cell)
{ {
public: unsigned int seed = Misc::Rng::rollDice(std::numeric_limits<int>::max());
MWWorld::ContainerStore mContainerStore; // setting ownership not needed, since taking items from a container inherits the
// container's owner automatically
virtual MWWorld::CustomData *clone() const; mStore.fillNonRandom(container.mInventory, "", seed);
}
virtual ContainerCustomData& asContainerCustomData() ContainerCustomData::ContainerCustomData(const ESM::InventoryState& inventory)
{ {
return *this; mStore.readState(inventory);
} }
virtual const ContainerCustomData& asContainerCustomData() const
{
return *this;
}
};
MWWorld::CustomData *ContainerCustomData::clone() const MWWorld::CustomData *ContainerCustomData::clone() const
{ {
return new ContainerCustomData (*this); return new ContainerCustomData (*this);
} }
ContainerCustomData& ContainerCustomData::asContainerCustomData()
{
return *this;
}
const ContainerCustomData& ContainerCustomData::asContainerCustomData() const
{
return *this;
}
void Container::ensureCustomData (const MWWorld::Ptr& ptr) const void Container::ensureCustomData (const MWWorld::Ptr& ptr) const
{ {
if (!ptr.getRefData().getCustomData()) if (!ptr.getRefData().getCustomData())
{ {
std::unique_ptr<ContainerCustomData> data (new ContainerCustomData); MWWorld::LiveCellRef<ESM::Container> *ref = ptr.get<ESM::Container>();
MWWorld::LiveCellRef<ESM::Container> *ref =
ptr.get<ESM::Container>();
// setting ownership not needed, since taking items from a container inherits the
// container's owner automatically
data->mContainerStore.fill(
ref->mBase->mInventory, "");
// store // store
ptr.getRefData().setCustomData (data.release()); ptr.getRefData().setCustomData (std::make_unique<ContainerCustomData>(*ref->mBase, ptr.getCell()).release());
MWBase::Environment::get().getWorld()->addContainerScripts(ptr, ptr.getCell()); MWBase::Environment::get().getWorld()->addContainerScripts(ptr, ptr.getCell());
} }
@ -98,17 +95,6 @@ namespace MWClass
} }
} }
void Container::restock(const MWWorld::Ptr& ptr) const
{
MWWorld::LiveCellRef<ESM::Container> *ref = ptr.get<ESM::Container>();
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 void Container::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const
{ {
if (!model.empty()) { if (!model.empty()) {
@ -228,12 +214,12 @@ namespace MWClass
return !name.empty() ? name : ref->mBase->mId; return !name.empty() ? name : ref->mBase->mId;
} }
MWWorld::ContainerStore& Container::getContainerStore (const MWWorld::Ptr& ptr) MWWorld::ContainerStore& Container::getContainerStore (const MWWorld::Ptr& ptr) const
const
{ {
ensureCustomData (ptr); ensureCustomData (ptr);
auto& data = ptr.getRefData().getCustomData()->asContainerCustomData();
return ptr.getRefData().getCustomData()->asContainerCustomData().mContainerStore; data.mStore.mPtr = ptr;
return data.mStore;
} }
std::string Container::getScript (const MWWorld::ConstPtr& ptr) const std::string Container::getScript (const MWWorld::ConstPtr& ptr) const
@ -253,8 +239,7 @@ namespace MWClass
bool Container::hasToolTip (const MWWorld::ConstPtr& ptr) const bool Container::hasToolTip (const MWWorld::ConstPtr& ptr) const
{ {
if (const MWWorld::CustomData* data = ptr.getRefData().getCustomData()) if (const MWWorld::CustomData* data = ptr.getRefData().getCustomData())
return !canBeHarvested(ptr) || data->asContainerCustomData().mContainerStore.hasVisibleItems(); return !canBeHarvested(ptr) || data->asContainerCustomData().mStore.hasVisibleItems();
return true; return true;
} }
@ -317,28 +302,20 @@ namespace MWClass
if (!state.mHasCustomState) if (!state.mHasCustomState)
return; return;
if (!ptr.getRefData().getCustomData())
{
// Create a CustomData, but don't fill it from ESM records (not needed)
std::unique_ptr<ContainerCustomData> data (new ContainerCustomData);
ptr.getRefData().setCustomData (data.release());
}
ContainerCustomData& customData = ptr.getRefData().getCustomData()->asContainerCustomData();
const ESM::ContainerState& containerState = state.asContainerState(); const ESM::ContainerState& containerState = state.asContainerState();
customData.mContainerStore.readState (containerState.mInventory); ptr.getRefData().setCustomData(std::make_unique<ContainerCustomData>(containerState.mInventory).release());
} }
void Container::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const 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; state.mHasCustomState = false;
return; return;
} }
const ContainerCustomData& customData = ptr.getRefData().getCustomData()->asContainerCustomData();
ESM::ContainerState& containerState = state.asContainerState(); ESM::ContainerState& containerState = state.asContainerState();
customData.mContainerStore.writeState (containerState.mInventory); customData.mStore.writeState (containerState.mInventory);
} }
} }

@ -2,9 +2,32 @@
#define GAME_MWCLASS_CONTAINER_H #define GAME_MWCLASS_CONTAINER_H
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/containerstore.hpp"
#include "../mwworld/customdata.hpp"
namespace ESM
{
struct Container;
struct InventoryState;
}
namespace MWClass 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 class Container : public MWWorld::Class
{ {
void ensureCustomData (const MWWorld::Ptr& ptr) const; void ensureCustomData (const MWWorld::Ptr& ptr) const;
@ -60,8 +83,6 @@ namespace MWClass
virtual void respawn (const MWWorld::Ptr& ptr) const; 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 std::string getModel(const MWWorld::ConstPtr &ptr) const;
virtual bool useAnim() const; virtual bool useAnim() const;

@ -842,14 +842,6 @@ namespace MWClass
} }
} }
void Creature::restock(const MWWorld::Ptr& ptr) const
{
MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();
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 int Creature::getBaseFightRating(const MWWorld::ConstPtr &ptr) const
{ {
const MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>(); const MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();

@ -123,8 +123,6 @@ namespace MWClass
virtual void respawn (const MWWorld::Ptr& ptr) const; 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 int getBaseFightRating(const MWWorld::ConstPtr &ptr) const;
virtual void adjustScale(const MWWorld::ConstPtr& ptr, osg::Vec3f& scale, bool rendering) const; virtual void adjustScale(const MWWorld::ConstPtr& ptr, osg::Vec3f& scale, bool rendering) const;

@ -1401,14 +1401,6 @@ namespace MWClass
} }
} }
void Npc::restock(const MWWorld::Ptr& ptr) const
{
MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
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 int Npc::getBaseFightRating (const MWWorld::ConstPtr& ptr) const
{ {
const MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>(); const MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();

@ -158,8 +158,6 @@ namespace MWClass
virtual void respawn (const MWWorld::Ptr& ptr) const; 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 int getBaseFightRating (const MWWorld::ConstPtr& ptr) const;
virtual std::string getPrimaryFaction(const MWWorld::ConstPtr &ptr) const; virtual std::string getPrimaryFaction(const MWWorld::ConstPtr &ptr) const;

@ -168,6 +168,7 @@ namespace MWGui
if (!mPtr.isEmpty()) if (!mPtr.isEmpty())
MWBase::Environment::get().getMechanicsManager()->onClose(mPtr); MWBase::Environment::get().getMechanicsManager()->onClose(mPtr);
resetReference();
} }
void ContainerWindow::onCloseButtonClicked(MyGUI::Widget* _sender) void ContainerWindow::onCloseButtonClicked(MyGUI::Widget* _sender)

@ -39,22 +39,29 @@ namespace
namespace MWGui namespace MWGui
{ {
ContainerItemModel::ContainerItemModel(const std::vector<MWWorld::Ptr>& itemSources, const std::vector<MWWorld::Ptr>& worldItems) ContainerItemModel::ContainerItemModel(const std::vector<MWWorld::Ptr>& itemSources, const std::vector<MWWorld::Ptr>& 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 bool ContainerItemModel::allowedToUseItems() const
{ {
if (mItemSources.size() == 0) if (mItemSources.empty())
return true; return true;
MWWorld::Ptr ptr = MWMechanics::getPlayer(); 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 // Check if the player is allowed to use items from opened container
MWBase::MechanicsManager* mm = MWBase::Environment::get().getMechanicsManager(); 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) 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) MWWorld::Ptr ContainerItemModel::copyItem (const ItemStack& item, size_t count, bool allowAutoEquip)
{ {
const MWWorld::Ptr& source = mItemSources[mItemSources.size()-1]; auto& source = mItemSources[0];
if (item.mBase.getContainerStore() == &source.getClass().getContainerStore(source)) 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!"); 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) void ContainerItemModel::removeItem (const ItemStack& item, size_t count)
{ {
int toRemove = 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) for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it)
{ {
if (stacks(*it, item.mBase)) 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) if (toRemove <= 0)
return; return;
} }
@ -138,9 +151,9 @@ void ContainerItemModel::removeItem (const ItemStack& item, size_t count)
void ContainerItemModel::update() void ContainerItemModel::update()
{ {
mItems.clear(); 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) 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()) if (mItemSources.empty())
return false; return false;
MWWorld::Ptr target = mItemSources[0]; MWWorld::Ptr target = mItemSources[0].first;
if (target.getTypeName() != typeid(ESM::Container).name()) if (target.getTypeName() != typeid(ESM::Container).name())
return true; return true;
@ -224,7 +237,7 @@ bool ContainerItemModel::onTakeItem(const MWWorld::Ptr &item, int count)
if (mItemSources.empty()) if (mItemSources.empty())
return false; return false;
MWWorld::Ptr target = mItemSources[0]; MWWorld::Ptr target = mItemSources[0].first;
// Looting a dead corpse is considered OK // Looting a dead corpse is considered OK
if (target.getClass().isActor() && target.getClass().getCreatureStats(target).isDead()) if (target.getClass().isActor() && target.getClass().getCreatureStats(target).isDead())

@ -1,8 +1,13 @@
#ifndef MWGUI_CONTAINER_ITEM_MODEL_H #ifndef MWGUI_CONTAINER_ITEM_MODEL_H
#define MWGUI_CONTAINER_ITEM_MODEL_H #define MWGUI_CONTAINER_ITEM_MODEL_H
#include <utility>
#include <vector>
#include "itemmodel.hpp" #include "itemmodel.hpp"
#include "../mwworld/containerstore.hpp"
namespace MWGui namespace MWGui
{ {
@ -32,9 +37,9 @@ namespace MWGui
virtual void update(); virtual void update();
private: private:
std::vector<MWWorld::Ptr> mItemSources; std::vector<std::pair<MWWorld::Ptr, MWWorld::ResolutionHandle>> mItemSources;
std::vector<MWWorld::Ptr> mWorldItems; std::vector<MWWorld::Ptr> mWorldItems;
const bool mTrading;
std::vector<ItemStack> mItems; std::vector<ItemStack> mItems;
}; };

@ -119,7 +119,7 @@ namespace MWGui
mBorrowedToUs.clear(); mBorrowedToUs.clear();
} }
std::vector<ItemStack> TradeItemModel::getItemsBorrowedToUs() const std::vector<ItemStack> TradeItemModel::getItemsBorrowedToUs() const
{ {
return mBorrowedToUs; return mBorrowedToUs;
} }

@ -40,7 +40,7 @@ namespace MWGui
/// and removing weight for items we've lent to someone else. /// and removing weight for items we've lent to someone else.
void adjustEncumbrance (float& encumbrance); void adjustEncumbrance (float& encumbrance);
std::vector<ItemStack> getItemsBorrowedToUs(); const std::vector<ItemStack> getItemsBorrowedToUs() const;
private: private:
void borrowImpl(const ItemStack& item, std::vector<ItemStack>& out); void borrowImpl(const ItemStack& item, std::vector<ItemStack>& out);

@ -99,20 +99,6 @@ namespace MWGui
setCoord(400, 0, 400, 300); 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<MWWorld::Ptr> itemSources;
MWBase::Environment::get().getWorld()->getContainersOwnedBy(mPtr, itemSources);
for (MWWorld::Ptr& source : itemSources)
{
source.getClass().restock(source);
}
}
void TradeWindow::setPtr(const MWWorld::Ptr& actor) void TradeWindow::setPtr(const MWWorld::Ptr& actor)
{ {
mPtr = actor; mPtr = actor;
@ -121,10 +107,10 @@ namespace MWGui
mCurrentMerchantOffer = 0; mCurrentMerchantOffer = 0;
std::vector<MWWorld::Ptr> itemSources; std::vector<MWWorld::Ptr> 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); 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<MWWorld::Ptr> worldItems; std::vector<MWWorld::Ptr> worldItems;
MWBase::Environment::get().getWorld()->getItemsOwnedBy(actor, worldItems); MWBase::Environment::get().getWorld()->getItemsOwnedBy(actor, worldItems);
@ -281,8 +267,8 @@ namespace MWGui
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>(); MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
// were there any items traded at all? // were there any items traded at all?
std::vector<ItemStack> playerBought = playerItemModel->getItemsBorrowedToUs(); const std::vector<ItemStack>& playerBought = playerItemModel->getItemsBorrowedToUs();
std::vector<ItemStack> merchantBought = mTradeModel->getItemsBorrowedToUs(); const std::vector<ItemStack>& merchantBought = mTradeModel->getItemsBorrowedToUs();
if (playerBought.empty() && merchantBought.empty()) if (playerBought.empty() && merchantBought.empty())
{ {
// user notification // user notification
@ -313,7 +299,7 @@ namespace MWGui
} }
// check if the player is attempting to sell back an item stolen from this actor // 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)) 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()->playSound("Item Gold Up");
MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Barter); MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Barter);
restock();
} }
void TradeWindow::onAccept(MyGUI::EditBox *sender) void TradeWindow::onAccept(MyGUI::EditBox *sender)
@ -478,7 +462,7 @@ namespace MWGui
// connected to buying and selling the same item. // connected to buying and selling the same item.
// This value has been determined by researching the limitations of the vanilla formula // This value has been determined by researching the limitations of the vanilla formula
// and may not be sufficient if getBarterOffer behavior has been changed. // and may not be sufficient if getBarterOffer behavior has been changed.
std::vector<ItemStack> playerBorrowed = playerTradeModel->getItemsBorrowedToUs(); const std::vector<ItemStack>& playerBorrowed = playerTradeModel->getItemsBorrowedToUs();
for (const ItemStack& itemStack : playerBorrowed) for (const ItemStack& itemStack : playerBorrowed)
{ {
const int basePrice = getEffectiveValue(itemStack.mBase, itemStack.mCount); const int basePrice = getEffectiveValue(itemStack.mBase, itemStack.mCount);
@ -487,7 +471,7 @@ namespace MWGui
merchantOffer -= std::max(cap, buyingPrice); merchantOffer -= std::max(cap, buyingPrice);
} }
std::vector<ItemStack> merchantBorrowed = mTradeModel->getItemsBorrowedToUs(); const std::vector<ItemStack>& merchantBorrowed = mTradeModel->getItemsBorrowedToUs();
for (const ItemStack& itemStack : merchantBorrowed) for (const ItemStack& itemStack : merchantBorrowed)
{ {
const int basePrice = getEffectiveValue(itemStack.mBase, itemStack.mCount); const int basePrice = getEffectiveValue(itemStack.mBase, itemStack.mCount);
@ -532,4 +516,9 @@ namespace MWGui
mTradeModel = nullptr; mTradeModel = nullptr;
mSortModel = nullptr; mSortModel = nullptr;
} }
void TradeWindow::onClose()
{
resetReference();
}
} }

@ -29,6 +29,7 @@ namespace MWGui
void setPtr(const MWWorld::Ptr& actor); void setPtr(const MWWorld::Ptr& actor);
virtual void onClose() override;
void onFrame(float dt); void onFrame(float dt);
void clear() { resetReference(); } void clear() { resetReference(); }
@ -111,8 +112,6 @@ namespace MWGui
virtual void onReferenceUnavailable(); virtual void onReferenceUnavailable();
int getMerchantGold(); int getMerchantGold();
void restock();
}; };
} }

@ -19,14 +19,14 @@ namespace MWMechanics
{ {
/// @return ID of resulting item, or empty if none /// @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<ESM::LevelledListBase::LevelItem>& items = levItem->mList; const std::vector<ESM::LevelledListBase::LevelItem>& items = levItem->mList;
const MWWorld::Ptr& player = getPlayer(); const MWWorld::Ptr& player = getPlayer();
int playerLevel = player.getClass().getCreatureStats(player).getLevel(); int playerLevel = player.getClass().getCreatureStats(player).getLevel();
if (Misc::Rng::roll0to99() < levItem->mChanceNone) if (Misc::Rng::roll0to99(seed) < levItem->mChanceNone)
return std::string(); return std::string();
std::vector<std::string> candidates; std::vector<std::string> candidates;
@ -55,7 +55,7 @@ namespace MWMechanics
} }
if (candidates.empty()) if (candidates.empty())
return std::string(); 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 // Vanilla doesn't fail on nonexistent items in levelled lists
if (!MWBase::Environment::get().getWorld()->getStore().find(Misc::StringUtils::lowerCase(item))) if (!MWBase::Environment::get().getWorld()->getStore().find(Misc::StringUtils::lowerCase(item)))
@ -74,9 +74,9 @@ namespace MWMechanics
else else
{ {
if (ref.getPtr().getTypeName() == typeid(ESM::ItemLevList).name()) if (ref.getPtr().getTypeName() == typeid(ESM::ItemLevList).name())
return getLevelledItem(ref.getPtr().get<ESM::ItemLevList>()->mBase, false); return getLevelledItem(ref.getPtr().get<ESM::ItemLevList>()->mBase, false, seed);
else else
return getLevelledItem(ref.getPtr().get<ESM::CreatureLevList>()->mBase, true); return getLevelledItem(ref.getPtr().get<ESM::CreatureLevList>()->mBase, true, seed);
} }
} }

@ -1045,6 +1045,7 @@ namespace MWMechanics
void MechanicsManager::confiscateStolenItems(const MWWorld::Ptr &player, const MWWorld::Ptr &targetContainer) void MechanicsManager::confiscateStolenItems(const MWWorld::Ptr &player, const MWWorld::Ptr &targetContainer)
{ {
MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); MWWorld::ContainerStore& store = player.getClass().getContainerStore(player);
MWWorld::ContainerStore& containerStore = targetContainer.getClass().getContainerStore(targetContainer);
for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it)
{ {
StolenItemsMap::iterator stolenIt = mStolenItems.find(Misc::StringUtils::lowerCase(it->getCellRef().getRefId())); StolenItemsMap::iterator stolenIt = mStolenItems.find(Misc::StringUtils::lowerCase(it->getCellRef().getRefId()));
@ -1065,7 +1066,7 @@ namespace MWMechanics
int toMove = it->getRefData().getCount() - itemCount; int toMove = it->getRefData().getCount() - itemCount;
targetContainer.getClass().getContainerStore(targetContainer).add(*it, toMove, targetContainer); containerStore.add(*it, toMove, targetContainer);
store.remove(*it, toMove, player); store.remove(*it, toMove, player);
} }
// TODO: unhardcode the locklevel // TODO: unhardcode the locklevel

@ -29,6 +29,7 @@ namespace MWWorld
MWWorld::Ptr target = getTarget(); MWWorld::Ptr target = getTarget();
MWWorld::ContainerStore& store = target.getClass().getContainerStore (target); MWWorld::ContainerStore& store = target.getClass().getContainerStore (target);
store.resolve();
MWWorld::ContainerStore& actorStore = actor.getClass().getContainerStore(actor); MWWorld::ContainerStore& actorStore = actor.getClass().getContainerStore(actor);
std::map<std::string, int> takenMap; std::map<std::string, int> takenMap;
for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it)

@ -1030,7 +1030,8 @@ namespace MWWorld
for (CellRefList<ESM::Container>::List::iterator it (mContainers.mList.begin()); it!=mContainers.mList.end(); ++it) for (CellRefList<ESM::Container>::List::iterator it (mContainers.mList.begin()); it!=mContainers.mList.end(); ++it)
{ {
Ptr ptr = getCurrentPtr(&*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); ptr.getClass().getContainerStore(ptr).rechargeItems(duration);
} }

@ -353,8 +353,6 @@ namespace MWWorld
virtual void respawn (const MWWorld::Ptr& ptr) const {} virtual void respawn (const MWWorld::Ptr& ptr) const {}
virtual void restock (const MWWorld::Ptr& ptr) const {}
/// Returns sound id /// Returns sound id
virtual std::string getSound(const MWWorld::ConstPtr& ptr) const; virtual std::string getSound(const MWWorld::ConstPtr& ptr) const;

@ -23,6 +23,21 @@
namespace 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<typename T> template<typename T>
float getTotalWeight (const MWWorld::CellRefList<T>& cellRefList) float getTotalWeight (const MWWorld::CellRefList<T>& cellRefList)
{ {
@ -44,6 +59,7 @@ namespace
MWWorld::Ptr searchId (MWWorld::CellRefList<T>& list, const std::string& id, MWWorld::Ptr searchId (MWWorld::CellRefList<T>& list, const std::string& id,
MWWorld::ContainerStore *store) MWWorld::ContainerStore *store)
{ {
store->resolve();
std::string id2 = Misc::StringUtils::lowerCase (id); std::string id2 = Misc::StringUtils::lowerCase (id);
for (typename MWWorld::CellRefList<T>::List::iterator iter (list.mList.begin()); for (typename MWWorld::CellRefList<T>::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<ESM::Container>()->mBase->mInventory, "", mStore.mSeed);
addScripts(mStore, mStore.mPtr.mCell);
mStore.mResolved = false;
}
}
template<typename T> template<typename T>
MWWorld::ContainerStoreIterator MWWorld::ContainerStore::getState (CellRefList<T>& collection, MWWorld::ContainerStoreIterator MWWorld::ContainerStore::getState (CellRefList<T>& collection,
const ESM::ObjectState& state) const ESM::ObjectState& state)
@ -119,7 +147,11 @@ MWWorld::ContainerStore::ContainerStore()
: mListener(nullptr) : mListener(nullptr)
, mRechargingItemsUpToDate(false) , mRechargingItemsUpToDate(false)
, mCachedWeight (0) , mCachedWeight (0)
, mWeightUpToDate (false) {} , mWeightUpToDate (false)
, mModified(false)
, mResolved(false)
, mSeed()
, mPtr() {}
MWWorld::ContainerStore::~ContainerStore() {} MWWorld::ContainerStore::~ContainerStore() {}
@ -153,22 +185,12 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::end()
return ContainerStoreIterator (this); 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; int total=0;
for (MWWorld::ContainerStoreIterator iter (begin()); iter!=end(); ++iter) for (const auto& iter : *this)
if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), id)) if (Misc::StringUtils::ciEqual(iter.getCellRef().getRefId(), id))
if (iter->getCellRef().getSoul().empty()) total += iter.getRefData().getCount();
total += iter->getRefData().getCount();
return total; 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) MWWorld::ContainerStoreIterator MWWorld::ContainerStore::unstack(const Ptr &ptr, const Ptr& container, int count)
{ {
resolve();
if (ptr.getRefData().getCount() <= count) if (ptr.getRefData().getCount() <= count)
return end(); 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); const std::string script = it->getClass().getScript(*it);
if (!script.empty()) if (!script.empty())
MWBase::Environment::get().getWorld()->getLocalScripts().add(script, *it); 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) MWWorld::ContainerStoreIterator MWWorld::ContainerStore::restack(const MWWorld::Ptr& item)
{ {
resolve();
MWWorld::ContainerStoreIterator retval = end(); MWWorld::ContainerStoreIterator retval = end();
for (MWWorld::ContainerStoreIterator iter (begin()); iter != end(); ++iter) for (MWWorld::ContainerStoreIterator iter (begin()); iter != end(); ++iter)
{ {
@ -216,7 +240,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::restack(const MWWorld::
{ {
if (stacks(*iter, item)) 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); item.getRefData().setCount(0);
retval = iter; retval = iter;
break; break;
@ -328,8 +352,10 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr
return it; 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); int type = getType(ptr);
const MWWorld::ESMStore &esmStore = 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)) 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(); flagAsModified();
return iter; return iter;
} }
@ -361,7 +387,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp (const Ptr& ptr,
if (stacks(*iter, ptr)) if (stacks(*iter, ptr))
{ {
// stack // stack
iter->getRefData().setCount( iter->getRefData().getCount() + count ); iter->getRefData().setCount(addItems(iter->getRefData().getCount(false), count));
flagAsModified(); flagAsModified();
return iter; return iter;
@ -439,6 +465,7 @@ void MWWorld::ContainerStore::updateRechargingItems()
int MWWorld::ContainerStore::remove(const std::string& itemId, int count, const Ptr& actor) int MWWorld::ContainerStore::remove(const std::string& itemId, int count, const Ptr& actor)
{ {
resolve();
int toRemove = count; int toRemove = count;
for (ContainerStoreIterator iter(begin()); iter != end() && toRemove > 0; ++iter) 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) int MWWorld::ContainerStore::remove(const Ptr& item, int count, const Ptr& actor)
{ {
assert(this == item.getContainerStore()); assert(this == item.getContainerStore());
resolve();
int toRemove = count; int toRemove = count;
RefData& itemRef = item.getRefData(); RefData& itemRef = item.getRefData();
@ -476,7 +504,7 @@ int MWWorld::ContainerStore::remove(const Ptr& item, int count, const Ptr& actor
} }
else else
{ {
itemRef.setCount(itemRef.getCount() - toRemove); itemRef.setCount(subtractItems(itemRef.getCount(false), toRemove));
toRemove = 0; toRemove = 0;
} }
@ -490,20 +518,33 @@ int MWWorld::ContainerStore::remove(const Ptr& item, int count, const Ptr& actor
return count - toRemove; 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 (const ESM::ContItem& iter : items.mList)
{
std::string id = Misc::StringUtils::lowerCase(iter.mItem);
addInitialItem(id, owner, iter.mCount, &seed);
}
flagAsModified();
mResolved = true;
}
void MWWorld::ContainerStore::fillNonRandom (const ESM::InventoryList& items, const std::string& owner, unsigned int seed)
{ {
for (std::vector<ESM::ContItem>::const_iterator iter (items.mList.begin()); iter!=items.mList.end(); mSeed = seed;
++iter) for (const ESM::ContItem& iter : items.mList)
{ {
std::string id = Misc::StringUtils::lowerCase(iter->mItem); std::string id = Misc::StringUtils::lowerCase(iter.mItem);
addInitialItem(id, owner, iter->mCount); addInitialItem(id, owner, iter.mCount, nullptr);
} }
flagAsModified(); flagAsModified();
mResolved = false;
} }
void MWWorld::ContainerStore::addInitialItem (const std::string& id, const std::string& owner, void MWWorld::ContainerStore::addInitialItem (const std::string& id, const std::string& owner, int count,
int count, bool topLevel, const std::string& levItem) Misc::Rng::Seed* seed, bool topLevel, const std::string& levItem)
{ {
if (count == 0) return; //Don't restock with nothing. if (count == 0) return; //Don't restock with nothing.
try try
@ -511,13 +552,13 @@ void MWWorld::ContainerStore::addInitialItem (const std::string& id, const std::
ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), id, count); ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), id, count);
if (ref.getPtr().getClass().getScript(ref.getPtr()).empty()) if (ref.getPtr().getClass().getScript(ref.getPtr()).empty())
{ {
addInitialItemImp(ref.getPtr(), owner, count, topLevel, levItem); addInitialItemImp(ref.getPtr(), owner, count, seed, topLevel, levItem);
} }
else else
{ {
// Adding just one item per time to make sure there isn't a stack of scripted items // 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++) for (int i = 0; i < std::abs(count); i++)
addInitialItemImp(ref.getPtr(), owner, count < 0 ? -1 : 1, topLevel, levItem); addInitialItemImp(ref.getPtr(), owner, count < 0 ? -1 : 1, seed, topLevel, levItem);
} }
} }
catch (const std::exception& e) 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, void MWWorld::ContainerStore::addInitialItemImp(const MWWorld::Ptr& ptr, const std::string& owner, int count,
int count, bool topLevel, const std::string& levItem) Misc::Rng::Seed* seed, bool topLevel, const std::string& levItem)
{ {
if (ptr.getTypeName()==typeid (ESM::ItemLevList).name()) if (ptr.getTypeName()==typeid (ESM::ItemLevList).name())
{ {
if(!seed)
return;
const ESM::ItemLevList* levItemList = ptr.get<ESM::ItemLevList>()->mBase; const ESM::ItemLevList* levItemList = ptr.get<ESM::ItemLevList>()->mBase;
if (topLevel && std::abs(count) > 1 && levItemList->mFlags & ESM::ItemLevList::Each) if (topLevel && std::abs(count) > 1 && levItemList->mFlags & ESM::ItemLevList::Each)
{ {
for (int i=0; i<std::abs(count); ++i) for (int i=0; i<std::abs(count); ++i)
addInitialItem(ptr.getCellRef().getRefId(), owner, count > 0 ? 1 : -1, true, levItemList->mId); addInitialItem(ptr.getCellRef().getRefId(), owner, count > 0 ? 1 : -1, seed, true, levItemList->mId);
return; return;
} }
else else
{ {
std::string itemId = MWMechanics::getLevelledItem(ptr.get<ESM::ItemLevList>()->mBase, false); std::string itemId = MWMechanics::getLevelledItem(ptr.get<ESM::ItemLevList>()->mBase, false, *seed);
if (itemId.empty()) if (itemId.empty())
return; return;
addInitialItem(itemId, owner, count, false, levItemList->mId); addInitialItem(itemId, owner, count, seed, false, levItemList->mId);
} }
} }
else 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<std::pair<std::string, std::string>, 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); 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<std::string, int> allowedForReplace;
//Check which lists need restocking:
for (std::map<std::pair<std::string, std::string>, 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<std::string, int>::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<std::string, int>::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<ESM::ContItem>::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<ESM::ItemLevList>().search(it->mItem))
{
std::map<std::string, int>::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() void MWWorld::ContainerStore::clear()
{ {
for (ContainerStoreIterator iter (begin()); iter!=end(); ++iter) for (ContainerStoreIterator iter (begin()); iter!=end(); ++iter)
iter->getRefData().setCount (0); iter->getRefData().setCount (0);
flagAsModified(); flagAsModified();
mModified = true;
} }
void MWWorld::ContainerStore::flagAsModified() void MWWorld::ContainerStore::flagAsModified()
@ -665,6 +612,45 @@ void MWWorld::ContainerStore::flagAsModified()
mRechargingItemsUpToDate = false; 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<ESM::Container>()->mBase->mInventory, "", seed);
addScripts(*this, mPtr.mCell);
}
mModified = true;
}
MWWorld::ResolutionHandle MWWorld::ContainerStore::resolveTemporarily()
{
if(mModified)
return {};
std::shared_ptr<ResolutionListener> listener = mResolutionListener.lock();
if(!listener)
{
listener = std::make_shared<ResolutionListener>(*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<ESM::Container>()->mBase->mInventory, "", seed);
addScripts(*this, mPtr.mCell);
}
return {listener};
}
float MWWorld::ContainerStore::getWeight() const float MWWorld::ContainerStore::getWeight() const
{ {
if (!mWeightUpToDate) if (!mWeightUpToDate)
@ -761,6 +747,7 @@ MWWorld::Ptr MWWorld::ContainerStore::findReplacement(const std::string& id)
MWWorld::Ptr MWWorld::ContainerStore::search (const std::string& id) MWWorld::Ptr MWWorld::ContainerStore::search (const std::string& id)
{ {
resolve();
{ {
Ptr ptr = searchId (potions, id, this); Ptr ptr = searchId (potions, id, this);
if (!ptr.isEmpty()) if (!ptr.isEmpty())
@ -836,6 +823,22 @@ MWWorld::Ptr MWWorld::ContainerStore::search (const std::string& id)
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 void MWWorld::ContainerStore::writeState (ESM::InventoryState& state) const
{ {
state.mItems.clear(); state.mItems.clear();
@ -853,13 +856,13 @@ void MWWorld::ContainerStore::writeState (ESM::InventoryState& state) const
storeStates (repairs, state, index); storeStates (repairs, state, index);
storeStates (weapons, state, index, true); storeStates (weapons, state, index, true);
storeStates (lights, state, index, true); storeStates (lights, state, index, true);
state.mLevelledItemMap = mLevelledItemMap;
} }
void MWWorld::ContainerStore::readState (const ESM::InventoryState& inventory) void MWWorld::ContainerStore::readState (const ESM::InventoryState& inventory)
{ {
clear(); clear();
mModified = true;
mResolved = true;
int index = 0; int index = 0;
for (std::vector<ESM::ObjectState>::const_iterator for (std::vector<ESM::ObjectState>::const_iterator
@ -893,9 +896,6 @@ void MWWorld::ContainerStore::readState (const ESM::InventoryState& inventory)
break; break;
} }
} }
mLevelledItemMap = inventory.mLevelledItemMap;
} }
template<class PtrType> template<class PtrType>

@ -3,6 +3,7 @@
#include <iterator> #include <iterator>
#include <map> #include <map>
#include <memory>
#include <utility> #include <utility>
#include <components/esm/loadalch.hpp> #include <components/esm/loadalch.hpp>
@ -18,6 +19,8 @@
#include <components/esm/loadrepa.hpp> #include <components/esm/loadrepa.hpp>
#include <components/esm/loadweap.hpp> #include <components/esm/loadweap.hpp>
#include <components/misc/rng.hpp>
#include "ptr.hpp" #include "ptr.hpp"
#include "cellreflist.hpp" #include "cellreflist.hpp"
@ -27,6 +30,11 @@ namespace ESM
struct InventoryState; struct InventoryState;
} }
namespace MWClass
{
class Container;
}
namespace MWWorld namespace MWWorld
{ {
class ContainerStore; class ContainerStore;
@ -37,6 +45,21 @@ namespace MWWorld
typedef ContainerStoreIteratorBase<Ptr> ContainerStoreIterator; typedef ContainerStoreIteratorBase<Ptr> ContainerStoreIterator;
typedef ContainerStoreIteratorBase<ConstPtr> ConstContainerStoreIterator; typedef ContainerStoreIteratorBase<ConstPtr> ConstContainerStoreIterator;
class ResolutionListener
{
ContainerStore& mStore;
public:
ResolutionListener(ContainerStore& store) : mStore(store) {}
~ResolutionListener();
};
class ResolutionHandle
{
std::shared_ptr<ResolutionListener> mListener;
public:
ResolutionHandle(std::shared_ptr<ResolutionListener> listener) : mListener(listener) {}
ResolutionHandle() {}
};
class ContainerStoreListener class ContainerStoreListener
{ {
@ -93,15 +116,18 @@ namespace MWWorld
MWWorld::CellRefList<ESM::Repair> repairs; MWWorld::CellRefList<ESM::Repair> repairs;
MWWorld::CellRefList<ESM::Weapon> weapons; MWWorld::CellRefList<ESM::Weapon> weapons;
std::map<std::pair<std::string, std::string>, 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 float mCachedWeight;
mutable bool mWeightUpToDate; 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 = ""); bool mModified;
void addInitialItemImp (const MWWorld::Ptr& ptr, const std::string& owner, int count, bool topLevel=true, const std::string& levItem = ""); bool mResolved;
unsigned int mSeed;
MWWorld::Ptr mPtr;
std::weak_ptr<ResolutionListener> 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<typename T> template<typename T>
ContainerStoreIterator getState (CellRefList<T>& collection, ContainerStoreIterator getState (CellRefList<T>& 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. /// 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. /// @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? ///< @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; ContainerStoreListener* getContListener() const;
void setContListener(ContainerStoreListener* listener); void setContListener(ContainerStoreListener* listener);
protected: protected:
ContainerStoreIterator addNewStack (const ConstPtr& ptr, int count); ContainerStoreIterator addNewStack (const ConstPtr& ptr, int count);
///< Add the item to this container (do not try to stack it onto existing items) ///< Add the item to this container (do not try to stack it onto existing items)
virtual void flagAsModified(); 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: public:
virtual bool stacks (const ConstPtr& ptr1, const ConstPtr& ptr2) const; virtual bool stacks (const ConstPtr& ptr1, const ConstPtr& ptr2) const;
///< @return true if the two specified objects can stack with each other ///< @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. ///< 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(); virtual void clear();
///< Empty container. ///< Empty container.
@ -220,8 +246,15 @@ namespace MWWorld
virtual void readState (const ESM::InventoryState& state); virtual void readState (const ESM::InventoryState& state);
bool isResolved() const;
void resolve();
ResolutionHandle resolveTemporarily();
friend class ContainerStoreIteratorBase<Ptr>; friend class ContainerStoreIteratorBase<Ptr>;
friend class ContainerStoreIteratorBase<ConstPtr>; friend class ContainerStoreIteratorBase<ConstPtr>;
friend class ResolutionListener;
friend class MWClass::Container;
}; };

@ -86,8 +86,9 @@ void MWWorld::InventoryStore::readEquipmentState(const MWWorld::ContainerStoreIt
// unstack if required // unstack if required
if (!allowedSlots.second && iter->getRefData().getCount() > 1) if (!allowedSlots.second && iter->getRefData().getCount() > 1)
{ {
MWWorld::ContainerStoreIterator newIter = addNewStack(*iter, 1); int count = iter->getRefData().getCount(false);
iter->getRefData().setCount(iter->getRefData().getCount()-1); MWWorld::ContainerStoreIterator newIter = addNewStack(*iter, count > 0 ? 1 : -1);
iter->getRefData().setCount(subtractItems(count, 1));
mSlots[slot] = newIter; mSlots[slot] = newIter;
} }
else else
@ -850,8 +851,8 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipItemQuantity(con
{ {
if (stacks(*iter, item) && !isEquipped(*iter)) if (stacks(*iter, item) && !isEquipped(*iter))
{ {
iter->getRefData().setCount(iter->getRefData().getCount() + count); iter->getRefData().setCount(addItems(iter->getRefData().getCount(false), count));
item.getRefData().setCount(item.getRefData().getCount() - count); item.getRefData().setCount(subtractItems(item.getRefData().getCount(false), count));
return iter; return iter;
} }
} }

@ -45,7 +45,7 @@ namespace
// Ignore containers without generated content // Ignore containers without generated content
if (containerPtr.getTypeName() == typeid(ESM::Container).name() && if (containerPtr.getTypeName() == typeid(ESM::Container).name() &&
containerPtr.getRefData().getCustomData() == nullptr) containerPtr.getRefData().getCustomData() == nullptr)
return false; return true;
MWWorld::ContainerStore& container = containerPtr.getClass().getContainerStore(containerPtr); MWWorld::ContainerStore& container = containerPtr.getClass().getContainerStore(containerPtr);
for(MWWorld::ContainerStoreIterator it = container.begin(); it != container.end(); ++it) for(MWWorld::ContainerStoreIterator it = container.begin(); it != container.end(); ++it)

@ -146,8 +146,10 @@ namespace MWWorld
return mBaseNode; return mBaseNode;
} }
int RefData::getCount() const int RefData::getCount(bool absolute) const
{ {
if(absolute)
return std::abs(mCount);
return mCount; return mCount;
} }

@ -86,7 +86,7 @@ namespace MWWorld
/// Set base node (can be a null pointer). /// Set base node (can be a null pointer).
void setBaseNode (SceneUtil::PositionAttitudeTransform* base); void setBaseNode (SceneUtil::PositionAttitudeTransform* base);
int getCount() const; int getCount(bool absolute = true) const;
void setLocals (const ESM::Script& script); void setLocals (const ESM::Script& script);

@ -36,7 +36,7 @@ namespace DetourNavigator
dtPolyRef resultRef = 0; dtPolyRef resultRef = 0;
osg::Vec3f resultPosition; osg::Vec3f resultPosition;
navMeshQuery.findRandomPointAroundCircle(startRef, start.ptr(), maxRadius, &queryFilter, navMeshQuery.findRandomPointAroundCircle(startRef, start.ptr(), maxRadius, &queryFilter,
&Misc::Rng::rollProbability, &resultRef, resultPosition.ptr()); []() { return Misc::Rng::rollProbability(); }, &resultRef, resultPosition.ptr());
if (resultRef == 0) if (resultRef == 0)
return boost::optional<osg::Vec3f>(); return boost::optional<osg::Vec3f>();

@ -3,6 +3,8 @@
#include "esmreader.hpp" #include "esmreader.hpp"
#include "esmwriter.hpp" #include "esmwriter.hpp"
#include <components/misc/stringops.hpp>
void ESM::InventoryState::load (ESMReader &esm) void ESM::InventoryState::load (ESMReader &esm)
{ {
// obsolete // obsolete
@ -106,6 +108,19 @@ void ESM::InventoryState::load (ESMReader &esm)
mSelectedEnchantItem = -1; mSelectedEnchantItem = -1;
esm.getHNOT(mSelectedEnchantItem, "SELE"); 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 void ESM::InventoryState::save (ESMWriter &esm) const

@ -4,7 +4,7 @@
#include "esmwriter.hpp" #include "esmwriter.hpp"
unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE; unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE;
int ESM::SavedGame::sCurrentFormat = 14; int ESM::SavedGame::sCurrentFormat = 15;
void ESM::SavedGame::load (ESMReader &esm) void ESM::SavedGame::load (ESMReader &esm)
{ {

@ -3,29 +3,44 @@
#include <chrono> #include <chrono>
#include <random> #include <random>
namespace
{
Misc::Rng::Seed sSeed;
}
namespace Misc 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) 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<float>(0, 1 - std::numeric_limits<float>::epsilon())(generator); return std::uniform_real_distribution<float>(0, 1 - std::numeric_limits<float>::epsilon())(sSeed.mGenerator);
} }
float Rng::rollClosedProbability() float Rng::rollClosedProbability(Seed& seed)
{ {
return std::uniform_real_distribution<float>(0, 1)(generator); return std::uniform_real_distribution<float>(0, 1)(sSeed.mGenerator);
} }
int Rng::rollDice(int max) int Rng::rollDice(int max, Seed& seed)
{ {
return max > 0 ? std::uniform_int_distribution<int>(0, max - 1)(generator) : 0; return max > 0 ? std::uniform_int_distribution<int>(0, max - 1)(sSeed.mGenerator) : 0;
} }
unsigned int Rng::generateDefaultSeed() unsigned int Rng::generateDefaultSeed()

@ -13,24 +13,32 @@ namespace Misc
class Rng class Rng
{ {
public: public:
class Seed
/// create a RNG {
static std::mt19937 generator; std::mt19937 mGenerator;
public:
Seed();
Seed(const Seed&) = delete;
Seed(unsigned int seed);
friend class Rng;
};
static Seed& getSeed();
/// seed the RNG /// seed the RNG
static void init(unsigned int seed = generateDefaultSeed()); static void init(unsigned int seed = generateDefaultSeed());
/// return value in range [0.0f, 1.0f) <- note open upper range. /// 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. /// 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. /// 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] /// 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 /// returns default seed for RNG
static unsigned int generateDefaultSeed(); static unsigned int generateDefaultSeed();

Loading…
Cancel
Save