diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 24f25e508e..4f411ad813 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -874,6 +874,11 @@ namespace MWClass MWMechanics::setBaseAISetting(id, setting, value); } + void Creature::modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) const + { + MWMechanics::modifyBaseInventory(actorId, itemId, amount); + } + float Creature::getWalkSpeed(const MWWorld::Ptr& ptr) const { const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index 9071b9a335..2d7aa5a19e 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -132,6 +132,8 @@ namespace MWClass virtual void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) const; + virtual void modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) const; + float getWalkSpeed(const MWWorld::Ptr& ptr) const final; float getRunSpeed(const MWWorld::Ptr& ptr) const final; diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 1525cf6962..bc29d09730 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1460,6 +1460,11 @@ namespace MWClass MWMechanics::setBaseAISetting(id, setting, value); } + void Npc::modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) const + { + MWMechanics::modifyBaseInventory(actorId, itemId, amount); + } + float Npc::getWalkSpeed(const MWWorld::Ptr& ptr) const { const GMST& gmst = getGmst(); diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index d92293acbe..d52afcd82a 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -167,6 +167,8 @@ namespace MWClass virtual void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) const; + virtual void modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) const; + float getWalkSpeed(const MWWorld::Ptr& ptr) const final; float getRunSpeed(const MWWorld::Ptr& ptr) const final; diff --git a/apps/openmw/mwmechanics/actorutil.hpp b/apps/openmw/mwmechanics/actorutil.hpp index cb77ef3eab..275a3a8143 100644 --- a/apps/openmw/mwmechanics/actorutil.hpp +++ b/apps/openmw/mwmechanics/actorutil.hpp @@ -1,6 +1,8 @@ #ifndef OPENMW_MWMECHANICS_ACTORUTIL_H #define OPENMW_MWMECHANICS_ACTORUTIL_H +#include + #include #include @@ -53,8 +55,34 @@ namespace MWMechanics MWBase::Environment::get().getWorld()->createOverrideRecord(copy); } + template + void modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) + { + ESM::NPC copy = *MWBase::Environment::get().getWorld()->getStore().get().find(actorId); + for(auto& it : copy.mInventory.mList) + { + if(Misc::StringUtils::ciEqual(it.mItem, itemId)) + { + int sign = it.mCount < 1 ? -1 : 1; + it.mCount = sign * std::max(it.mCount * sign + amount, 0); + MWBase::Environment::get().getWorld()->createOverrideRecord(copy); + return; + } + } + if(amount > 0) + { + ESM::ContItem cont; + cont.mItem = itemId; + cont.mCount = amount; + copy.mInventory.mList.push_back(cont); + MWBase::Environment::get().getWorld()->createOverrideRecord(copy); + } + } + template void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value); 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); } #endif diff --git a/apps/openmw/mwscript/containerextensions.cpp b/apps/openmw/mwscript/containerextensions.cpp index 7116b1c9f3..9ed9204ad6 100644 --- a/apps/openmw/mwscript/containerextensions.cpp +++ b/apps/openmw/mwscript/containerextensions.cpp @@ -60,6 +60,13 @@ namespace MWScript || ::Misc::StringUtils::ciEqual(item, "gold_100")) item = "gold_001"; + // 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) + { + ptr.getClass().modifyBaseInventory(ptr.getCellRef().getRefId(), item, count); + 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); @@ -147,6 +154,13 @@ namespace MWScript || ::Misc::StringUtils::ciEqual(item, "gold_100")) item = "gold_001"; + // 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) + { + ptr.getClass().modifyBaseInventory(ptr.getCellRef().getRefId(), item, -count); + return; + } + MWWorld::ContainerStore& store = ptr.getClass().getContainerStore (ptr); std::string itemName; diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index b59532f2af..ad8766d06c 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -522,6 +522,11 @@ namespace MWWorld throw std::runtime_error ("class does not have creature stats"); } + void Class::modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) const + { + throw std::runtime_error ("class does not have an inventory store"); + } + float Class::getWalkSpeed(const Ptr& /*ptr*/) const { return 0; diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index f92dc03734..e82712220d 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -363,6 +363,8 @@ namespace MWWorld virtual void setBaseAISetting(const std::string& id, MWMechanics::CreatureStats::AiSetting setting, int value) const; + virtual void modifyBaseInventory(const std::string& actorId, const std::string& itemId, int amount) const; + virtual float getWalkSpeed(const Ptr& ptr) const; virtual float getRunSpeed(const Ptr& ptr) const; diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 278b8532e9..f6fccba92a 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -9,6 +9,45 @@ #include #include +namespace +{ + void readRefs(const ESM::Cell& cell, std::map& refs, std::vector& readers) + { + for (size_t i = 0; i < cell.mContextList.size(); i++) + { + size_t index = cell.mContextList[i].index; + if (readers.size() <= index) + readers.resize(index + 1); + cell.restore(readers[index], i); + ESM::CellRef ref; + ref.mRefNum.mContentFile = ESM::RefNum::RefNum_NoContentFile; + bool deleted = false; + while(cell.getNextRef(readers[index], ref, deleted)) + { + if(deleted) + refs.erase(ref.mRefNum); + else if (std::find(cell.mMovedRefs.begin(), cell.mMovedRefs.end(), ref.mRefNum) == cell.mMovedRefs.end()) + { + Misc::StringUtils::lowerCaseInPlace(ref.mRefID); + refs[ref.mRefNum] = ref.mRefID; + } + } + } + for(const auto& it : cell.mLeasedRefs) + { + bool deleted = it.second; + if(deleted) + refs.erase(it.first.mRefNum); + else + { + ESM::CellRef ref = it.first; + Misc::StringUtils::lowerCaseInPlace(ref.mRefID); + refs[ref.mRefNum] = ref.mRefID; + } + } + } +} + namespace MWWorld { @@ -146,7 +185,33 @@ void ESMStore::setUp(bool validateRecords) mDialogs.setUp(); if (validateRecords) + { validate(); + countRecords(); + } +} + +void ESMStore::countRecords() +{ + if(!mRefCount.empty()) + return; + std::map refs; + std::vector readers; + for(auto it = mCells.intBegin(); it != mCells.intEnd(); it++) + readRefs(*it, refs, readers); + for(auto it = mCells.extBegin(); it != mCells.extEnd(); it++) + readRefs(*it, refs, readers); + for(const auto& pair : refs) + mRefCount[pair.second]++; +} + +int ESMStore::getRefCount(const std::string& id) const +{ + const std::string lowerId = Misc::StringUtils::lowerCase(id); + auto it = mRefCount.find(lowerId); + if(it == mRefCount.end()) + return 0; + return it->second; } void ESMStore::validate() diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index 24364bfb13..b6c78f0429 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -70,6 +70,8 @@ namespace MWWorld std::map mIds; std::map mStaticIds; + std::map mRefCount; + std::map mStores; ESM::NPC mPlayerTemplate; @@ -79,6 +81,7 @@ namespace MWWorld /// Validate entries in store after setup void validate(); + void countRecords(); public: /// \todo replace with SharedIterator typedef std::map::const_iterator iterator; @@ -252,6 +255,8 @@ namespace MWWorld // To be called when we are done with dynamic record loading void checkPlayer(); + + int getRefCount(const std::string& id) const; }; template <>