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 #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

@ -31,44 +31,41 @@
namespace MWClass
{
class ContainerCustomData : public MWWorld::CustomData
ContainerCustomData::ContainerCustomData(const ESM::Container& container, MWWorld::CellStore* cell)
{
public:
MWWorld::ContainerStore mContainerStore;
virtual MWWorld::CustomData *clone() const;
unsigned int seed = Misc::Rng::rollDice(std::numeric_limits<int>::max());
// setting ownership not needed, since taking items from a container inherits the
// container's owner automatically
mStore.fillNonRandom(container.mInventory, "", seed);
}
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<ContainerCustomData> data (new ContainerCustomData);
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, "");
MWWorld::LiveCellRef<ESM::Container> *ref = ptr.get<ESM::Container>();
// 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());
}
@ -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
{
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<ContainerCustomData> 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<ContainerCustomData>(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);
}
}

@ -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;

@ -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
{
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 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;

@ -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
{
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 restock (const MWWorld::Ptr& ptr) const;
virtual int getBaseFightRating (const MWWorld::ConstPtr& ptr) const;
virtual std::string getPrimaryFaction(const MWWorld::ConstPtr &ptr) const;

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

@ -39,22 +39,29 @@ namespace
namespace MWGui
{
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
{
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())

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

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

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

@ -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<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)
{
mPtr = actor;
@ -121,10 +107,10 @@ namespace MWGui
mCurrentMerchantOffer = 0;
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);
// Important: actor goes last, so that items purchased by the merchant go into his inventory
itemSources.push_back(actor);
std::vector<MWWorld::Ptr> worldItems;
MWBase::Environment::get().getWorld()->getItemsOwnedBy(actor, worldItems);
@ -281,8 +267,8 @@ namespace MWGui
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
// were there any items traded at all?
std::vector<ItemStack> playerBought = playerItemModel->getItemsBorrowedToUs();
std::vector<ItemStack> merchantBought = mTradeModel->getItemsBorrowedToUs();
const std::vector<ItemStack>& playerBought = playerItemModel->getItemsBorrowedToUs();
const std::vector<ItemStack>& 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<ItemStack> playerBorrowed = playerTradeModel->getItemsBorrowedToUs();
const std::vector<ItemStack>& 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<ItemStack> merchantBorrowed = mTradeModel->getItemsBorrowedToUs();
const std::vector<ItemStack>& 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();
}
}

@ -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();
};
}

@ -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<ESM::LevelledListBase::LevelItem>& 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<std::string> 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<ESM::ItemLevList>()->mBase, false);
return getLevelledItem(ref.getPtr().get<ESM::ItemLevList>()->mBase, false, seed);
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)
{
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

@ -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<std::string, int> takenMap;
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)
{
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);
}

@ -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;

@ -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<typename T>
float getTotalWeight (const MWWorld::CellRefList<T>& cellRefList)
{
@ -44,6 +59,7 @@ namespace
MWWorld::Ptr searchId (MWWorld::CellRefList<T>& list, const std::string& id,
MWWorld::ContainerStore *store)
{
store->resolve();
std::string id2 = Misc::StringUtils::lowerCase (id);
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>
MWWorld::ContainerStoreIterator MWWorld::ContainerStore::getState (CellRefList<T>& 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 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 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))
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 (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();
++iter)
mSeed = seed;
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, nullptr);
}
flagAsModified();
mResolved = false;
}
void MWWorld::ContainerStore::addInitialItem (const std::string& id, const std::string& owner,
int count, bool topLevel, const std::string& levItem)
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<ESM::ItemLevList>()->mBase;
if (topLevel && std::abs(count) > 1 && levItemList->mFlags & ESM::ItemLevList::Each)
{
for (int i=0; i<std::abs(count); ++i)
addInitialItem(ptr.getCellRef().getRefId(), owner, count > 0 ? 1 : -1, 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<ESM::ItemLevList>()->mBase, false);
std::string itemId = MWMechanics::getLevelledItem(ptr.get<ESM::ItemLevList>()->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<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);
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()
{
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<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
{
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<ESM::ObjectState>::const_iterator
@ -893,9 +896,6 @@ void MWWorld::ContainerStore::readState (const ESM::InventoryState& inventory)
break;
}
}
mLevelledItemMap = inventory.mLevelledItemMap;
}
template<class PtrType>

@ -3,6 +3,7 @@
#include <iterator>
#include <map>
#include <memory>
#include <utility>
#include <components/esm/loadalch.hpp>
@ -18,6 +19,8 @@
#include <components/esm/loadrepa.hpp>
#include <components/esm/loadweap.hpp>
#include <components/misc/rng.hpp>
#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<Ptr> ContainerStoreIterator;
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
{
@ -93,15 +116,18 @@ namespace MWWorld
MWWorld::CellRefList<ESM::Repair> repairs;
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 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<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>
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.
/// @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<Ptr>;
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
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;
}
}

@ -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)

@ -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;
}

@ -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);

@ -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<osg::Vec3f>();

@ -3,6 +3,8 @@
#include "esmreader.hpp"
#include "esmwriter.hpp"
#include <components/misc/stringops.hpp>
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

@ -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)
{

@ -3,29 +3,44 @@
#include <chrono>
#include <random>
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<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()

@ -13,24 +13,32 @@ namespace Misc
class Rng
{
public:
/// create a RNG
static std::mt19937 generator;
class Seed
{
std::mt19937 mGenerator;
public:
Seed();
Seed(const Seed&) = delete;
Seed(unsigned int seed);
friend class Rng;
};
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();

Loading…
Cancel
Save