diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index ac572b35b0..1194402b91 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -337,14 +337,17 @@ namespace MWBase ///< Toggle a render mode. ///< \return Resulting mode - virtual MWWorld::Ptr placeObject(const MWWorld::ConstPtr& object, float cursorX, float cursorY, int amount) = 0; + virtual MWWorld::Ptr placeObject( + const MWWorld::Ptr& object, float cursorX, float cursorY, int amount, bool copy = true) + = 0; ///< copy and place an object into the gameworld at the specified cursor position /// @param object /// @param cursor X (relative 0-1) /// @param cursor Y (relative 0-1) /// @param number of objects to place - virtual MWWorld::Ptr dropObjectOnGround(const MWWorld::Ptr& actor, const MWWorld::ConstPtr& object, int amount) + virtual MWWorld::Ptr dropObjectOnGround( + const MWWorld::Ptr& actor, const MWWorld::Ptr& object, int amount, bool copy = true) = 0; ///< copy and place an object into the gameworld at the given actor's position /// @param actor giving the dropped object position diff --git a/apps/openmw/mwgui/companionitemmodel.cpp b/apps/openmw/mwgui/companionitemmodel.cpp index 1172de5897..6bae8aef52 100644 --- a/apps/openmw/mwgui/companionitemmodel.cpp +++ b/apps/openmw/mwgui/companionitemmodel.cpp @@ -25,6 +25,14 @@ namespace MWGui { } + MWWorld::Ptr CompanionItemModel::addItem(const ItemStack& item, size_t count, bool allowAutoEquip) + { + if (hasProfit(mActor)) + modifyProfit(mActor, item.mBase.getClass().getValue(item.mBase) * count); + + return InventoryItemModel::addItem(item, count, allowAutoEquip); + } + MWWorld::Ptr CompanionItemModel::copyItem(const ItemStack& item, size_t count, bool allowAutoEquip) { if (hasProfit(mActor)) diff --git a/apps/openmw/mwgui/companionitemmodel.hpp b/apps/openmw/mwgui/companionitemmodel.hpp index 0a9491c348..b9ee500693 100644 --- a/apps/openmw/mwgui/companionitemmodel.hpp +++ b/apps/openmw/mwgui/companionitemmodel.hpp @@ -13,6 +13,7 @@ namespace MWGui public: CompanionItemModel(const MWWorld::Ptr& actor); + MWWorld::Ptr addItem(const ItemStack& item, size_t count, bool allowAutoEquip = true) override; MWWorld::Ptr copyItem(const ItemStack& item, size_t count, bool allowAutoEquip = true) override; void removeItem(const ItemStack& item, size_t count) override; diff --git a/apps/openmw/mwgui/containeritemmodel.cpp b/apps/openmw/mwgui/containeritemmodel.cpp index b0abe5c733..ba4f7156c9 100644 --- a/apps/openmw/mwgui/containeritemmodel.cpp +++ b/apps/openmw/mwgui/containeritemmodel.cpp @@ -99,13 +99,22 @@ namespace MWGui return -1; } + MWWorld::Ptr ContainerItemModel::addItem(const ItemStack& item, size_t count, bool allowAutoEquip) + { + auto& source = mItemSources[0]; + MWWorld::ContainerStore& store = source.first.getClass().getContainerStore(source.first); + if (item.mBase.getContainerStore() == &store) + throw std::runtime_error("Item to add needs to be from a different container!"); + return *store.add(item.mBase, count, allowAutoEquip); + } + MWWorld::Ptr ContainerItemModel::copyItem(const ItemStack& item, size_t count, bool allowAutoEquip) { 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 *store.add(item.mBase, count, allowAutoEquip); + return *store.add(item.mBase.getCellRef().getRefId(), count, allowAutoEquip); } void ContainerItemModel::removeItem(const ItemStack& item, size_t count) diff --git a/apps/openmw/mwgui/containeritemmodel.hpp b/apps/openmw/mwgui/containeritemmodel.hpp index 1bdcec90f9..8e6f5a37ef 100644 --- a/apps/openmw/mwgui/containeritemmodel.hpp +++ b/apps/openmw/mwgui/containeritemmodel.hpp @@ -32,6 +32,7 @@ namespace MWGui ModelIndex getIndex(const ItemStack& item) override; size_t getItemCount() override; + MWWorld::Ptr addItem(const ItemStack& item, size_t count, bool allowAutoEquip = true) override; MWWorld::Ptr copyItem(const ItemStack& item, size_t count, bool allowAutoEquip = true) override; void removeItem(const ItemStack& item, size_t count) override; diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index 4df0e1dba4..022840ea5e 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -45,20 +45,31 @@ namespace MWGui { } virtual ~WorldItemModel() override {} - MWWorld::Ptr copyItem(const ItemStack& item, size_t count, bool /*allowAutoEquip*/) override + + MWWorld::Ptr dropItemImpl(const ItemStack& item, size_t count, bool copy) { MWBase::World* world = MWBase::Environment::get().getWorld(); MWWorld::Ptr dropped; if (world->canPlaceObject(mLeft, mTop)) - dropped = world->placeObject(item.mBase, mLeft, mTop, count); + dropped = world->placeObject(item.mBase, mLeft, mTop, count, copy); else - dropped = world->dropObjectOnGround(world->getPlayerPtr(), item.mBase, count); + dropped = world->dropObjectOnGround(world->getPlayerPtr(), item.mBase, count, copy); dropped.getCellRef().setOwner(ESM::RefId()); return dropped; } + MWWorld::Ptr addItem(const ItemStack& item, size_t count, bool /*allowAutoEquip*/) override + { + return dropItemImpl(item, count, false); + } + + MWWorld::Ptr copyItem(const ItemStack& item, size_t count, bool /*allowAutoEquip*/) override + { + return dropItemImpl(item, count, true); + } + void removeItem(const ItemStack& item, size_t count) override { throw std::runtime_error("removeItem not implemented"); diff --git a/apps/openmw/mwgui/inventoryitemmodel.cpp b/apps/openmw/mwgui/inventoryitemmodel.cpp index bcd51cd1b8..e87ae7b8e0 100644 --- a/apps/openmw/mwgui/inventoryitemmodel.cpp +++ b/apps/openmw/mwgui/inventoryitemmodel.cpp @@ -8,6 +8,7 @@ #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/inventorystore.hpp" +#include "../mwworld/manualref.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" @@ -46,11 +47,19 @@ namespace MWGui return -1; } + MWWorld::Ptr InventoryItemModel::addItem(const ItemStack& item, size_t count, bool allowAutoEquip) + { + if (item.mBase.getContainerStore() == &mActor.getClass().getContainerStore(mActor)) + throw std::runtime_error("Item to add needs to be from a different container!"); + return *mActor.getClass().getContainerStore(mActor).add(item.mBase, count, allowAutoEquip); + } + MWWorld::Ptr InventoryItemModel::copyItem(const ItemStack& item, size_t count, bool allowAutoEquip) { if (item.mBase.getContainerStore() == &mActor.getClass().getContainerStore(mActor)) throw std::runtime_error("Item to copy needs to be from a different container!"); - return *mActor.getClass().getContainerStore(mActor).add(item.mBase, count, allowAutoEquip); + return *mActor.getClass().getContainerStore(mActor).add( + item.mBase.getCellRef().getRefId(), count, allowAutoEquip); } void InventoryItemModel::removeItem(const ItemStack& item, size_t count) @@ -83,16 +92,15 @@ namespace MWGui } } - MWWorld::Ptr InventoryItemModel::moveItem(const ItemStack& item, size_t count, ItemModel* otherModel) + MWWorld::Ptr InventoryItemModel::moveItem( + const ItemStack& item, size_t count, ItemModel* otherModel, bool allowAutoEquip) { // Can't move conjured items: This is a general fix that also takes care of issues with taking conjured items // via the 'Take All' button. if (item.mFlags & ItemStack::Flag_Bound) return MWWorld::Ptr(); - MWWorld::Ptr ret = otherModel->copyItem(item, count); - removeItem(item, count); - return ret; + return ItemModel::moveItem(item, count, otherModel, allowAutoEquip); } void InventoryItemModel::update() diff --git a/apps/openmw/mwgui/inventoryitemmodel.hpp b/apps/openmw/mwgui/inventoryitemmodel.hpp index b99bfc544d..560d1a0fbd 100644 --- a/apps/openmw/mwgui/inventoryitemmodel.hpp +++ b/apps/openmw/mwgui/inventoryitemmodel.hpp @@ -17,11 +17,13 @@ namespace MWGui bool onTakeItem(const MWWorld::Ptr& item, int count) override; + MWWorld::Ptr addItem(const ItemStack& item, size_t count, bool allowAutoEquip = true) override; MWWorld::Ptr copyItem(const ItemStack& item, size_t count, bool allowAutoEquip = true) override; void removeItem(const ItemStack& item, size_t count) override; /// Move items from this model to \a otherModel. - MWWorld::Ptr moveItem(const ItemStack& item, size_t count, ItemModel* otherModel) override; + MWWorld::Ptr moveItem( + const ItemStack& item, size_t count, ItemModel* otherModel, bool allowAutoEquip = true) override; void update() override; diff --git a/apps/openmw/mwgui/itemmodel.cpp b/apps/openmw/mwgui/itemmodel.cpp index 9b9815c98a..fce3e68e78 100644 --- a/apps/openmw/mwgui/itemmodel.cpp +++ b/apps/openmw/mwgui/itemmodel.cpp @@ -2,10 +2,13 @@ #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" +#include "../mwworld/worldmodel.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include + namespace MWGui { @@ -55,12 +58,22 @@ namespace MWGui ItemModel::ItemModel() {} - MWWorld::Ptr ItemModel::moveItem(const ItemStack& item, size_t count, ItemModel* otherModel) + MWWorld::Ptr ItemModel::moveItem(const ItemStack& item, size_t count, ItemModel* otherModel, bool allowAutoEquip) { - // TODO(#6148): moving an item should preserve RefNum and Lua scripts (unless the item stack is merged with - // already existing stack). - MWWorld::Ptr ret = otherModel->copyItem(item, count); - removeItem(item, count); + MWWorld::Ptr ret = MWWorld::Ptr(); + if (static_cast(item.mBase.getRefData().getCount()) <= count) + { + // We are moving the full stack + ret = otherModel->addItem(item, count, allowAutoEquip); + removeItem(item, count); + } + else + { + // We are moving only part of the stack, so create a copy in the other model + // and then remove count from this model. + ret = otherModel->copyItem(item, count, allowAutoEquip); + removeItem(item, count); + } return ret; } @@ -143,6 +156,11 @@ namespace MWGui return mSourceModel->onTakeItem(item, count); } + MWWorld::Ptr ProxyItemModel::addItem(const ItemStack& item, size_t count, bool allowAutoEquip) + { + return mSourceModel->addItem(item, count, allowAutoEquip); + } + bool ProxyItemModel::usesContainer(const MWWorld::Ptr& container) { return mSourceModel->usesContainer(container); diff --git a/apps/openmw/mwgui/itemmodel.hpp b/apps/openmw/mwgui/itemmodel.hpp index c7143b4b91..7af29878eb 100644 --- a/apps/openmw/mwgui/itemmodel.hpp +++ b/apps/openmw/mwgui/itemmodel.hpp @@ -63,8 +63,10 @@ namespace MWGui /// Move items from this model to \a otherModel. /// @note Derived implementations may return an empty Ptr if the move was unsuccessful. - virtual MWWorld::Ptr moveItem(const ItemStack& item, size_t count, ItemModel* otherModel); + virtual MWWorld::Ptr moveItem( + const ItemStack& item, size_t count, ItemModel* otherModel, bool allowAutoEquip = true); + virtual MWWorld::Ptr addItem(const ItemStack& item, size_t count, bool allowAutoEquip = true) = 0; virtual MWWorld::Ptr copyItem(const ItemStack& item, size_t count, bool allowAutoEquip = true) = 0; virtual void removeItem(const ItemStack& item, size_t count) = 0; @@ -95,6 +97,7 @@ namespace MWGui bool onDropItem(const MWWorld::Ptr& item, int count) override; bool onTakeItem(const MWWorld::Ptr& item, int count) override; + MWWorld::Ptr addItem(const ItemStack& item, size_t count, bool allowAutoEquip = true) override; MWWorld::Ptr copyItem(const ItemStack& item, size_t count, bool allowAutoEquip = true) override; void removeItem(const ItemStack& item, size_t count) override; ModelIndex getIndex(const ItemStack& item) override; diff --git a/apps/openmw/mwgui/tradeitemmodel.cpp b/apps/openmw/mwgui/tradeitemmodel.cpp index f14d7fe72c..50a55f5061 100644 --- a/apps/openmw/mwgui/tradeitemmodel.cpp +++ b/apps/openmw/mwgui/tradeitemmodel.cpp @@ -139,11 +139,8 @@ namespace MWGui if (i == sourceModel->getItemCount()) throw std::runtime_error("The borrowed item disappeared"); - const ItemStack& item = sourceModel->getItem(i); - // copy the borrowed items to our model - copyItem(item, itemStack.mCount, !Settings::game().mPreventMerchantEquipping); - // then remove them from the source model - sourceModel->removeItem(item, itemStack.mCount); + sourceModel->moveItem( + sourceModel->getItem(i), itemStack.mCount, this, !Settings::game().mPreventMerchantEquipping); } mBorrowedToUs.clear(); mBorrowedFromUs.clear(); diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 03e624a608..28d351902b 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -391,10 +391,20 @@ namespace MWWorld return newPtr; } + Ptr Class::moveToCell(const Ptr& ptr, CellStore& cell, const ESM::Position& pos, int count) const + { + Ptr newPtr = moveToCell(ptr, cell); + newPtr.getRefData().setPosition(pos); + newPtr.getCellRef().setPosition(pos); + newPtr.getRefData().setCount(count); + return newPtr; + } + MWWorld::Ptr Class::copyToCell(const ConstPtr& ptr, CellStore& cell, const ESM::Position& pos, int count) const { Ptr newPtr = copyToCell(ptr, cell, count); newPtr.getRefData().setPosition(pos); + newPtr.getCellRef().setPosition(pos); return newPtr; } diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index fbd2b98a74..9aa72be041 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -317,6 +317,7 @@ namespace MWWorld // The original is expected to be removed after calling this function, // but this function itself doesn't remove the original. virtual Ptr moveToCell(const Ptr& ptr, CellStore& cell) const; + Ptr moveToCell(const Ptr& ptr, CellStore& cell, const ESM::Position& pos, int count) const; Ptr copyToCell(const ConstPtr& ptr, CellStore& cell, const ESM::Position& pos, int count) const; diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index b429bc31ed..b3ea654404 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -300,10 +300,10 @@ bool MWWorld::ContainerStore::stacks(const ConstPtr& ptr1, const ConstPtr& ptr2) && cls2.getItemHealth(ptr2) == cls2.getItemMaxHealth(ptr2))); } -MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add(const ESM::RefId& id, int count) +MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add(const ESM::RefId& id, int count, bool allowAutoEquip) { MWWorld::ManualRef ref(*MWBase::Environment::get().getESMStore(), id, count); - return add(ref.getPtr(), count); + return add(ref.getPtr(), count, allowAutoEquip); } MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add( diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index 9d02e0b95c..253df39567 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -198,7 +198,7 @@ namespace MWWorld /// @return if stacking happened, return iterator to the item that was stacked against, otherwise iterator to /// the newly inserted item. - ContainerStoreIterator add(const ESM::RefId& id, int count); + ContainerStoreIterator add(const ESM::RefId& id, int count, bool allowAutoEquip = true); ///< Utility to construct a ManualRef and call add(ptr, count, actorPtr, true) int remove(const ESM::RefId& itemId, int count, bool equipReplacement = 0, bool resolve = true); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index d0e8ab2301..b03ac01cc8 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2001,7 +2001,7 @@ namespace MWWorld item.getRefData().getLocals().setVarByInt(script, "onpcdrop", 1); } - MWWorld::Ptr World::placeObject(const MWWorld::ConstPtr& object, float cursorX, float cursorY, int amount) + MWWorld::Ptr World::placeObject(const MWWorld::Ptr& object, float cursorX, float cursorY, int amount, bool copy) { const float maxDist = 200.f; @@ -2022,7 +2022,8 @@ namespace MWWorld pos.rot[1] = 0; // copy the object and set its count - Ptr dropped = copyObjectToCell(object, cell, pos, amount, true); + Ptr dropped = copy ? copyObjectToCell(object, cell, pos, amount, true) + : moveObjectToCell(object, cell, pos, amount, true); // only the player place items in the world, so no need to check actor PCDropped(dropped); @@ -2062,33 +2063,55 @@ namespace MWWorld MWWorld::Ptr dropped = object.getClass().copyToCell(object, *cell, pos, count); - // Reset some position values that could be uninitialized if this item came from a container - dropped.getCellRef().setPosition(pos); - dropped.getCellRef().unsetRefNum(); + initObjectInCell(dropped, *cell, adjustPos); - if (mWorldScene->isCellActive(*cell)) + return dropped; + } + + Ptr World::moveObjectToCell(const Ptr& object, CellStore* cell, ESM::Position pos, int count, bool adjustPos) + { + if (!cell) + throw std::runtime_error("moveObjectToCell(): cannot move object to null cell"); + if (cell->isExterior()) + { + const ESM::ExteriorCellLocation index + = ESM::positionToExteriorCellLocation(pos.pos[0], pos.pos[1], cell->getCell()->getWorldSpace()); + cell = &mWorldModel.getExterior(index); + } + + MWWorld::Ptr dropped = object.getClass().moveToCell(object, *cell, pos, count); + + initObjectInCell(dropped, *cell, adjustPos); + + return dropped; + } + + void World::initObjectInCell(const Ptr& object, CellStore& cell, bool adjustPos) + { + if (mWorldScene->isCellActive(cell)) { - if (dropped.getRefData().isEnabled()) + if (object.getRefData().isEnabled()) { - mWorldScene->addObjectToScene(dropped); + mWorldScene->addObjectToScene(object); } - const auto& script = dropped.getClass().getScript(dropped); + const auto& script = object.getClass().getScript(object); if (!script.empty()) { - mLocalScripts.add(script, dropped); + mLocalScripts.add(script, object); } - addContainerScripts(dropped, cell); + addContainerScripts(object, &cell); } - if (!object.getClass().isActor() && adjustPos && dropped.getRefData().getBaseNode()) + if (!object.getClass().isActor() && adjustPos && object.getRefData().getBaseNode()) { // Adjust position so the location we wanted ends up in the middle of the object bounding box osg::ComputeBoundsVisitor computeBounds; computeBounds.setTraversalMask(~MWRender::Mask_ParticleSystem); - dropped.getRefData().getBaseNode()->accept(computeBounds); + object.getRefData().getBaseNode()->accept(computeBounds); osg::BoundingBox bounds = computeBounds.getBoundingBox(); if (bounds.valid()) { + ESM::Position pos = object.getRefData().getPosition(); bounds.set(bounds._min - pos.asVec3(), bounds._max - pos.asVec3()); osg::Vec3f adjust( @@ -2096,14 +2119,12 @@ namespace MWWorld pos.pos[0] -= adjust.x(); pos.pos[1] -= adjust.y(); pos.pos[2] -= adjust.z(); - moveObject(dropped, pos.asVec3()); + moveObject(object, pos.asVec3()); } } - - return dropped; } - MWWorld::Ptr World::dropObjectOnGround(const Ptr& actor, const ConstPtr& object, int amount) + MWWorld::Ptr World::dropObjectOnGround(const Ptr& actor, const Ptr& object, int amount, bool copy) { MWWorld::CellStore* cell = actor.getCell(); @@ -2123,7 +2144,8 @@ namespace MWWorld pos.pos[2] = result.mHitPointWorld.z(); // copy the object and set its count - Ptr dropped = copyObjectToCell(object, cell, pos, amount, true); + Ptr dropped = copy ? copyObjectToCell(object, cell, pos, amount, true) + : moveObjectToCell(object, cell, pos, amount, true); if (actor == mPlayer->getPlayer()) // Only call if dropped by player PCDropped(dropped); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index e15f5808d3..30d3102cc2 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -143,6 +143,8 @@ namespace MWWorld void updateWeather(float duration, bool paused = false); + void initObjectInCell(const Ptr& ptr, CellStore& cell, bool adjustPos); + Ptr moveObjectToCell(const Ptr& ptr, CellStore* cell, ESM::Position pos, int count, bool adjustPos); Ptr copyObjectToCell(const ConstPtr& ptr, CellStore* cell, ESM::Position pos, int count, bool adjustPos); void updateSoundListener(); @@ -425,7 +427,8 @@ namespace MWWorld void updateWindowManager(); - MWWorld::Ptr placeObject(const MWWorld::ConstPtr& object, float cursorX, float cursorY, int amount) override; + MWWorld::Ptr placeObject( + const MWWorld::Ptr& object, float cursorX, float cursorY, int amount, bool copy = true) override; ///< copy and place an object into the gameworld at the specified cursor position /// @param object /// @param cursor X (relative 0-1) @@ -433,7 +436,7 @@ namespace MWWorld /// @param number of objects to place MWWorld::Ptr dropObjectOnGround( - const MWWorld::Ptr& actor, const MWWorld::ConstPtr& object, int amount) override; + const MWWorld::Ptr& actor, const MWWorld::Ptr& object, int amount, bool copy = true) override; ///< copy and place an object into the gameworld at the given actor's position /// @param actor giving the dropped object position /// @param object