From 09373a757d6b128c3bc86b1f9b1334e180d7bec4 Mon Sep 17 00:00:00 2001 From: psi29a Date: Tue, 20 Oct 2020 09:22:43 +0000 Subject: [PATCH] Merge branch 'radioactive' into 'master' Container base record mutations See merge request OpenMW/openmw!353 (cherry picked from commit 8b33765dd414680f0074b3e115b52b291b4cb7cb) 275908a0 mutate container base records 16fca11d add changelog entry --- CHANGELOG.md | 1 + apps/openmw/mwbase/world.hpp | 7 ++ apps/openmw/mwclass/container.cpp | 6 ++ apps/openmw/mwclass/container.hpp | 2 + apps/openmw/mwmechanics/actorutil.hpp | 2 + apps/openmw/mwscript/containerextensions.cpp | 87 +++++++++++++++++--- apps/openmw/mwworld/cells.cpp | 52 ++++++++++++ apps/openmw/mwworld/cells.hpp | 2 + apps/openmw/mwworld/containerstore.cpp | 16 ++-- apps/openmw/mwworld/containerstore.hpp | 6 +- apps/openmw/mwworld/inventorystore.cpp | 4 +- apps/openmw/mwworld/inventorystore.hpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 10 +++ apps/openmw/mwworld/worldimp.hpp | 6 ++ 14 files changed, 181 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 882c8199e6..39f6684a99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ 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 #2798: Mutable ESM records 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 diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 70e65e0eac..f9cbc89723 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -36,6 +36,7 @@ namespace ESM struct Position; struct Cell; struct Class; + struct Container; struct Creature; struct Potion; struct Spell; @@ -385,6 +386,10 @@ namespace MWBase ///< Write this record to the ESM store, allowing it to override a pre-existing record with the same ID. /// \return pointer to created record + virtual const ESM::Container *createOverrideRecord (const ESM::Container& record) = 0; + ///< Write this record to the ESM store, allowing it to override a pre-existing record with the same ID. + /// \return pointer to created record + virtual void update (float duration, bool paused) = 0; virtual void updatePhysics (float duration, bool paused) = 0; @@ -641,6 +646,8 @@ namespace MWBase virtual bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const = 0; virtual void reportStats(unsigned int frameNumber, osg::Stats& stats) const = 0; + + virtual std::vector getAll(const std::string& id) = 0; }; } diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 9befa06363..a27e3debdd 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -27,6 +27,7 @@ #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" +#include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/npcstats.hpp" namespace MWClass @@ -290,6 +291,11 @@ namespace MWClass return !(ref->mBase->mFlags & ESM::Container::Organic); } + void Container::modifyBaseInventory(const std::string& containerId, const std::string& itemId, int amount) const + { + MWMechanics::modifyBaseInventory(containerId, itemId, amount); + } + MWWorld::Ptr Container::copyToCellImpl(const MWWorld::ConstPtr &ptr, MWWorld::CellStore &cell) const { const MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/container.hpp b/apps/openmw/mwclass/container.hpp index 54775e2b09..57dbf0c76c 100644 --- a/apps/openmw/mwclass/container.hpp +++ b/apps/openmw/mwclass/container.hpp @@ -84,6 +84,8 @@ namespace MWClass std::string getModel(const MWWorld::ConstPtr &ptr) const override; bool useAnim() const override; + + void modifyBaseInventory(const std::string& containerId, const std::string& itemId, int amount) const override; }; } diff --git a/apps/openmw/mwmechanics/actorutil.hpp b/apps/openmw/mwmechanics/actorutil.hpp index 490dc119a7..1e993f5606 100644 --- a/apps/openmw/mwmechanics/actorutil.hpp +++ b/apps/openmw/mwmechanics/actorutil.hpp @@ -3,6 +3,7 @@ #include +#include #include #include @@ -83,6 +84,7 @@ namespace MWMechanics template void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value); template void modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount); template void modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount); + template void modifyBaseInventory(const std::string& containerId, const std::string& itemId, int amount); } #endif diff --git a/apps/openmw/mwscript/containerextensions.cpp b/apps/openmw/mwscript/containerextensions.cpp index 7b5bf47300..0af5cee2b2 100644 --- a/apps/openmw/mwscript/containerextensions.cpp +++ b/apps/openmw/mwscript/containerextensions.cpp @@ -19,15 +19,35 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" +#include "../mwclass/container.hpp" + #include "../mwworld/action.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/inventorystore.hpp" +#include "../mwworld/manualref.hpp" #include "../mwmechanics/actorutil.hpp" #include "ref.hpp" +namespace +{ + void addToStore(MWWorld::Ptr& itemPtr, int count, MWWorld::Ptr& ptr, MWWorld::ContainerStore& store, bool resolve = true) + { + if (itemPtr.getClass().getScript(itemPtr).empty()) + { + store.add (itemPtr, count, ptr, true, resolve); + } + else + { + // Adding just one item per time to make sure there isn't a stack of scripted items + for (int i = 0; i < count; i++) + store.add (itemPtr, 1, ptr, true, resolve); + } + } +} + namespace MWScript { namespace Container @@ -60,6 +80,13 @@ namespace MWScript || ::Misc::StringUtils::ciEqual(item, "gold_100")) item = "gold_001"; + // Check if "item" can be placed in a container + MWWorld::ManualRef manualRef(MWBase::Environment::get().getWorld()->getStore(), item, 1); + MWWorld::Ptr itemPtr = manualRef.getPtr(); + bool isLevelledList = itemPtr.getClass().getTypeName() == typeid(ESM::ItemLevList).name(); + if(!isLevelledList) + MWWorld::ContainerStore::getType(itemPtr); + // Explicit calls to non-unique actors affect the base record if(!R::implicit && ptr.getClass().isActor() && MWBase::Environment::get().getWorld()->getStore().getRefCount(ptr.getCellRef().getRefId()) > 1) { @@ -67,19 +94,36 @@ namespace MWScript return; } - MWWorld::ContainerStore& store = ptr.getClass().getContainerStore (ptr); - // Create a Ptr for the first added item to recover the item name later - MWWorld::Ptr itemPtr = *store.add (item, 1, ptr); - if (itemPtr.getClass().getScript(itemPtr).empty()) + // Calls to unresolved containers affect the base record + if(ptr.getClass().getTypeName() == typeid(ESM::Container).name() && (!ptr.getRefData().getCustomData() || + !ptr.getClass().getContainerStore(ptr).isResolved())) { - store.add (item, count-1, ptr); - } - else - { - // Adding just one item per time to make sure there isn't a stack of scripted items - for (int i = 1; i < count; i++) - store.add (item, 1, ptr); + ptr.getClass().modifyBaseInventory(ptr.getCellRef().getRefId(), item, count); + const ESM::Container* baseRecord = MWBase::Environment::get().getWorld()->getStore().get().find(ptr.getCellRef().getRefId()); + const auto& ptrs = MWBase::Environment::get().getWorld()->getAll(ptr.getCellRef().getRefId()); + for(const auto& container : ptrs) + { + // use the new base record + container.get()->mBase = baseRecord; + if(container.getRefData().getCustomData()) + { + auto& store = container.getClass().getContainerStore(container); + if(isLevelledList) + { + if(store.isResolved()) + { + // TODO #2404 + } + } + else + addToStore(itemPtr, count, ptr, store, store.isResolved()); + } + } + return; } + MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); + // TODO #2404 + addToStore(itemPtr, count, ptr, store); // Spawn a messagebox (only for items added to player's inventory and if player is talking to someone) if (ptr == MWBase::Environment::get().getWorld ()->getPlayerPtr() ) @@ -160,7 +204,26 @@ namespace MWScript ptr.getClass().modifyBaseInventory(ptr.getCellRef().getRefId(), item, -count); return; } - + // Calls to unresolved containers affect the base record instead + else if(ptr.getClass().getTypeName() == typeid(ESM::Container).name() && + (!ptr.getRefData().getCustomData() || !ptr.getClass().getContainerStore(ptr).isResolved())) + { + ptr.getClass().modifyBaseInventory(ptr.getCellRef().getRefId(), item, -count); + const ESM::Container* baseRecord = MWBase::Environment::get().getWorld()->getStore().get().find(ptr.getCellRef().getRefId()); + const auto& ptrs = MWBase::Environment::get().getWorld()->getAll(ptr.getCellRef().getRefId()); + for(const auto& container : ptrs) + { + container.get()->mBase = baseRecord; + if(container.getRefData().getCustomData()) + { + auto& store = container.getClass().getContainerStore(container); + // Note that unlike AddItem, RemoveItem only removes from unresolved containers + if(!store.isResolved()) + store.remove(item, count, ptr, false); + } + } + return; + } MWWorld::ContainerStore& store = ptr.getClass().getContainerStore (ptr); std::string itemName; diff --git a/apps/openmw/mwworld/cells.cpp b/apps/openmw/mwworld/cells.cpp index 0603b9de50..94c78b4332 100644 --- a/apps/openmw/mwworld/cells.cpp +++ b/apps/openmw/mwworld/cells.cpp @@ -16,6 +16,50 @@ #include "containerstore.hpp" #include "cellstore.hpp" +namespace +{ + template + bool forEachInStore(const std::string& id, Visitor&& visitor, std::map& cellStore) + { + for(auto& cell : cellStore) + { + if(cell.second.getState() == MWWorld::CellStore::State_Unloaded) + cell.second.preload(); + if(cell.second.getState() == MWWorld::CellStore::State_Preloaded) + { + if(cell.second.hasId(id)) + { + cell.second.load(); + } + else + continue; + } + bool cont = cell.second.forEach([&] (MWWorld::Ptr ptr) + { + if(*ptr.getCellRef().getRefIdPtr() == id) + { + return visitor(ptr); + } + return true; + }); + if(!cont) + return false; + } + return true; + } + + struct PtrCollector + { + std::vector mPtrs; + + bool operator()(MWWorld::Ptr ptr) + { + mPtrs.emplace_back(ptr); + return true; + } + }; +} + MWWorld::CellStore *MWWorld::Cells::getCellStore (const ESM::Cell *cell) { if (cell->mData.mFlags & ESM::Cell::Interior) @@ -330,6 +374,14 @@ void MWWorld::Cells::getInteriorPtrs(const std::string &name, std::vector MWWorld::Cells::getAll(const std::string& id) +{ + PtrCollector visitor; + if(forEachInStore(id, visitor, mInteriors)) + forEachInStore(id, visitor, mExteriors); + return visitor.mPtrs; +} + int MWWorld::Cells::countSavedGameRecords() const { int count = 0; diff --git a/apps/openmw/mwworld/cells.hpp b/apps/openmw/mwworld/cells.hpp index 3e64ad9753..90ede409b2 100644 --- a/apps/openmw/mwworld/cells.hpp +++ b/apps/openmw/mwworld/cells.hpp @@ -80,6 +80,8 @@ namespace MWWorld /// @note name must be lower case void getInteriorPtrs (const std::string& name, std::vector& out); + std::vector getAll(const std::string& id); + int countSavedGameRecords() const; void write (ESM::ESMWriter& writer, Loading::Listener& progress) const; diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index 5ecb3dd3a7..17eb6a3725 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -290,11 +290,11 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add(const std::string & return add(ref.getPtr(), count, actorPtr); } -MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr, int count, const Ptr& actorPtr, bool /*allowAutoEquip*/) +MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr, int count, const Ptr& actorPtr, bool /*allowAutoEquip*/, bool resolve) { Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); - MWWorld::ContainerStoreIterator it = addImp(itemPtr, count); + MWWorld::ContainerStoreIterator it = addImp(itemPtr, count, resolve); // The copy of the original item we just made MWWorld::Ptr item = *it; @@ -463,14 +463,15 @@ 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, bool resolveFirst) { - resolve(); + if(resolveFirst) + resolve(); int toRemove = count; for (ContainerStoreIterator iter(begin()); iter != end() && toRemove > 0; ++iter) if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), itemId)) - toRemove -= remove(*iter, toRemove, actor); + toRemove -= removeImp(*iter, toRemove, actor); flagAsModified(); @@ -494,6 +495,11 @@ int MWWorld::ContainerStore::remove(const Ptr& item, int count, const Ptr& actor assert(this == item.getContainerStore()); resolve(); + return removeImp(item, count, actor); +} + +int MWWorld::ContainerStore::removeImp(const Ptr& item, int count, const Ptr& actor) +{ int toRemove = count; RefData& itemRef = item.getRefData(); diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index c89e855f91..be73603b24 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -129,6 +129,8 @@ namespace MWWorld 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 = ""); + int removeImp(const Ptr& item, int count, const Ptr& actor); + template ContainerStoreIterator getState (CellRefList& collection, const ESM::ObjectState& state); @@ -165,7 +167,7 @@ namespace MWWorld bool hasVisibleItems() const; - virtual ContainerStoreIterator add (const Ptr& itemPtr, int count, const Ptr& actorPtr, bool allowAutoEquip = true); + virtual ContainerStoreIterator add (const Ptr& itemPtr, int count, const Ptr& actorPtr, bool allowAutoEquip = true, bool resolve = true); ///< Add the item pointed to by \a ptr to this container. (Stacks automatically if needed) /// /// \note The item pointed to is not required to exist beyond this function call. @@ -178,7 +180,7 @@ namespace MWWorld ContainerStoreIterator add(const std::string& id, int count, const Ptr& actorPtr); ///< Utility to construct a ManualRef and call add(ptr, count, actorPtr, true) - int remove(const std::string& itemId, int count, const Ptr& actor); + int remove(const std::string& itemId, int count, const Ptr& actor, bool resolve = true); ///< Remove \a count item(s) designated by \a itemId from this container. /// /// @return the number of items actually removed diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index cf2b97ce7a..c2785579b6 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -132,9 +132,9 @@ MWWorld::InventoryStore& MWWorld::InventoryStore::operator= (const InventoryStor return *this; } -MWWorld::ContainerStoreIterator MWWorld::InventoryStore::add(const Ptr& itemPtr, int count, const Ptr& actorPtr, bool allowAutoEquip) +MWWorld::ContainerStoreIterator MWWorld::InventoryStore::add(const Ptr& itemPtr, int count, const Ptr& actorPtr, bool allowAutoEquip, bool resolve) { - const MWWorld::ContainerStoreIterator& retVal = MWWorld::ContainerStore::add(itemPtr, count, actorPtr, allowAutoEquip); + const MWWorld::ContainerStoreIterator& retVal = MWWorld::ContainerStore::add(itemPtr, count, actorPtr, allowAutoEquip, resolve); // Auto-equip items if an armor/clothing item is added, but not for the player nor werewolves if (allowAutoEquip && actorPtr != MWMechanics::getPlayer() diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index 6d669dd452..12306f809c 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -125,7 +125,7 @@ namespace MWWorld InventoryStore* clone() override { return new InventoryStore(*this); } - ContainerStoreIterator add (const Ptr& itemPtr, int count, const Ptr& actorPtr, bool allowAutoEquip = true) override; + ContainerStoreIterator add (const Ptr& itemPtr, int count, const Ptr& actorPtr, bool allowAutoEquip = true, bool resolve = true) override; ///< Add the item pointed to by \a ptr to this container. (Stacks automatically if needed) /// Auto-equip items if specific conditions are fulfilled and allowAutoEquip is true (see the implementation). /// diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index dcd282f8d9..dbe6af3268 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1735,6 +1735,11 @@ namespace MWWorld return mStore.overrideRecord(record); } + const ESM::Container *World::createOverrideRecord(const ESM::Container &record) + { + return mStore.overrideRecord(record); + } + const ESM::NPC *World::createRecord(const ESM::NPC &record) { bool update = false; @@ -3910,4 +3915,9 @@ namespace MWWorld ESM::EpochTimeStamp currentDate = mCurrentDate->getEpochTimeStamp(); mRendering->skySetDate(currentDate.mDay, currentDate.mMonth); } + + std::vector World::getAll(const std::string& id) + { + return mCells.getAll(id); + } } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index c10c7ea5a1..909ac1d412 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -488,6 +488,10 @@ namespace MWWorld ///< Write this record to the ESM store, allowing it to override a pre-existing record with the same ID. /// \return pointer to created record + const ESM::Container *createOverrideRecord (const ESM::Container& record) override; + ///< Write this record to the ESM store, allowing it to override a pre-existing record with the same ID. + /// \return pointer to created record + void update (float duration, bool paused) override; void updatePhysics (float duration, bool paused) override; @@ -733,6 +737,8 @@ namespace MWWorld bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const override; void reportStats(unsigned int frameNumber, osg::Stats& stats) const override; + + std::vector getAll(const std::string& id) override; }; }