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/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..3620ceaf69 100644 --- a/apps/openmw/mwgui/inventoryitemmodel.cpp +++ b/apps/openmw/mwgui/inventoryitemmodel.cpp @@ -46,13 +46,29 @@ namespace MWGui return -1; } - MWWorld::Ptr InventoryItemModel::copyItem(const ItemStack& item, size_t count, bool allowAutoEquip) + 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 copy needs to be from a different container!"); + 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) + { + // TODO: This does not copy the item, but adds it directly. This will duplicate the item's + // refnum and other ref data unless the caller handles that. + return addItem(item, count, allowAutoEquip); + } + + MWWorld::Ptr InventoryItemModel::unstackItem(const ItemStack& item, size_t count) + { + MWWorld::ContainerStore& store = mActor.getClass().getContainerStore(mActor); + auto it = store.unstack(item.mBase, count); + if (it != store.end()) + return *it; + return MWWorld::Ptr(); + } + void InventoryItemModel::removeItem(const ItemStack& item, size_t count) { int removed = 0; diff --git a/apps/openmw/mwgui/inventoryitemmodel.hpp b/apps/openmw/mwgui/inventoryitemmodel.hpp index b99bfc544d..78cd0a739e 100644 --- a/apps/openmw/mwgui/inventoryitemmodel.hpp +++ b/apps/openmw/mwgui/inventoryitemmodel.hpp @@ -17,7 +17,9 @@ 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; + MWWorld::Ptr unstackItem(const ItemStack& item, size_t count) override; void removeItem(const ItemStack& item, size_t count) override; /// Move items from this model to \a otherModel. diff --git a/apps/openmw/mwgui/itemmodel.cpp b/apps/openmw/mwgui/itemmodel.cpp index 9b9815c98a..12f1b5842d 100644 --- a/apps/openmw/mwgui/itemmodel.cpp +++ b/apps/openmw/mwgui/itemmodel.cpp @@ -57,13 +57,25 @@ namespace MWGui MWWorld::Ptr ItemModel::moveItem(const ItemStack& item, size_t count, ItemModel* otherModel) { - // 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); + MWWorld::Ptr ret = otherModel->addItem(item, count); + // Unstacking here ensures that new a refnum is assigned to the leftover stack if there is a leftover. + // Otherwise we end up with duplicated instances. + unstackItem(item, count); removeItem(item, count); return ret; } + MWWorld::Ptr ItemModel::addItem(const ItemStack& item, size_t count, bool allowAutoEquip) + { + return copyItem(item, count, allowAutoEquip); + } + + MWWorld::Ptr ItemModel::unstackItem(const ItemStack& item, size_t count) + { + // By default does nothing + return MWWorld::Ptr(); + } + bool ItemModel::allowedToUseItems() const { return true; @@ -143,6 +155,16 @@ namespace MWGui return mSourceModel->onTakeItem(item, count); } + MWWorld::Ptr ProxyItemModel::unstackItem(const ItemStack& item, size_t count) + { + return mSourceModel->unstackItem(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..5ea3ce78e3 100644 --- a/apps/openmw/mwgui/itemmodel.hpp +++ b/apps/openmw/mwgui/itemmodel.hpp @@ -65,6 +65,11 @@ namespace MWGui /// @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); + /// Unstacks items from this model and returns a ptr to the new remainder stack. + /// @note Returns en empty ptr if there is no remainder or the item model does not support unstacking. + virtual MWWorld::Ptr unstackItem(const ItemStack& item, size_t count); + + virtual MWWorld::Ptr addItem(const ItemStack& item, size_t count, bool allowAutoEquip = true); 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 +100,8 @@ namespace MWGui bool onDropItem(const MWWorld::Ptr& item, int count) override; bool onTakeItem(const MWWorld::Ptr& item, int count) override; + MWWorld::Ptr unstackItem(const ItemStack& item, size_t 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/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 3f246628cc..23b507f4dc 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,30 +2063,65 @@ namespace MWWorld MWWorld::Ptr dropped = object.getClass().copyToCell(object, *cell, pos, count); + addObjectToCell(dropped, cell, pos, adjustPos); + + 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); + dropped.getRefData().setCount(count); + dropped.getRefData().setPosition(pos); + + addObjectToCell(dropped, cell, pos, adjustPos); + + return dropped; + } + + void World::addObjectToCell(const Ptr& object, CellStore* cell, ESM::Position pos, bool adjustPos) + { + if (!cell) + throw std::runtime_error("addObjectToCell(): cannot add 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); + } + // Reset some position values that could be uninitialized if this item came from a container - dropped.getCellRef().setPosition(pos); - dropped.getCellRef().unsetRefNum(); + object.getCellRef().setPosition(pos); 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()) { @@ -2096,14 +2132,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 +2157,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..b2ea5986fa 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 addObjectToCell(const Ptr& ptr, CellStore* cell, ESM::Position pos, 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