From 9a0415ad6b4eb76e728ca254ac731df00ffb10d7 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Fri, 20 Feb 2026 17:41:19 -0800 Subject: [PATCH] Fix bag bar drag/drop with container bags Allow equipped bags to be moved through the shared inventory pickup/drop flow, including dragging from bag contents to bag bar and back from bag bar into bag/backpack slots. Changes: - Add InventoryScreen APIs to begin pickup from an equipment slot and drop held item into a target equipment slot. - Treat inventory type 18 (bags) as valid drops on BAG1-BAG4 during slot validation. - Route equipment placement in online mode through swapContainerItems with explicit src/dst addressing for deterministic bag slot moves. - Update bag bar hover-drop path to use InventoryScreen drop API instead of direct local slot mutation. - On bag bar drag start, hand off to inventory held-item drag when inventory/character UI is open so drops into bag/backpack slots work. --- include/ui/inventory_screen.hpp | 5 ++ src/ui/game_screen.cpp | 22 +++++-- src/ui/inventory_screen.cpp | 101 ++++++++++++++++++++++---------- 3 files changed, 93 insertions(+), 35 deletions(-) diff --git a/include/ui/inventory_screen.hpp b/include/ui/inventory_screen.hpp index 0d1c03fb..237b10e0 100644 --- a/include/ui/inventory_screen.hpp +++ b/include/ui/inventory_screen.hpp @@ -166,8 +166,13 @@ public: bool isHoldingItem() const { return holdingItem; } /// Returns the item being held (only valid when isHoldingItem() is true). const game::ItemDef& getHeldItem() const { return heldItem; } + /// Begin pickup from an equipment slot (e.g., bag bar slot) into held cursor. + bool beginPickupFromEquipSlot(game::Inventory& inv, game::EquipSlot slot); /// Cancel the pickup, returning the item to its original slot. void returnHeldItem(game::Inventory& inv) { cancelPickup(inv); } + /// Drop the currently held item into a specific equipment slot. + /// Returns true if the drop was accepted and consumed. + bool dropHeldItemToEquipSlot(game::Inventory& inv, game::EquipSlot slot); }; } // namespace ui diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index 6d36ff7b..30f24194 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -3670,10 +3670,10 @@ void GameScreen::renderBagBar(game::GameHandler& gameHandler) { // Accept dragged item from inventory if (hovered && inventoryScreen.isHoldingItem()) { const auto& heldItem = inventoryScreen.getHeldItem(); - if (heldItem.bagSlots > 0 && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) { + if ((heldItem.inventoryType == 18 || heldItem.bagSlots > 0) && + ImGui::IsMouseReleased(ImGuiMouseButton_Left)) { auto& inventory = gameHandler.getInventory(); - inventory.setEquipSlot(bagSlot, heldItem); - inventoryScreen.returnHeldItem(inventory); + inventoryScreen.dropHeldItemToEquipSlot(inventory, bagSlot); } } @@ -3685,8 +3685,20 @@ void GameScreen::renderBagBar(game::GameHandler& gameHandler) { // releasing completes swap or click if (bagBarDragSource_ >= 0) { if (ImGui::IsMouseDragging(ImGuiMouseButton_Left, 3.0f) && bagBarPickedSlot_ < 0) { - // Mouse moved enough — start visual drag - bagBarPickedSlot_ = bagBarDragSource_; + // If an inventory window is open, hand off drag to inventory held-item + // so the bag can be dropped into backpack/bag slots. + if (inventoryScreen.isOpen() || inventoryScreen.isCharacterOpen()) { + auto equip = static_cast( + static_cast(game::EquipSlot::BAG1) + bagBarDragSource_); + if (inventoryScreen.beginPickupFromEquipSlot(inv, equip)) { + bagBarDragSource_ = -1; + } else { + bagBarPickedSlot_ = bagBarDragSource_; + } + } else { + // Mouse moved enough — start visual drag + bagBarPickedSlot_ = bagBarDragSource_; + } } if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) { if (bagBarPickedSlot_ >= 0) { diff --git a/src/ui/inventory_screen.cpp b/src/ui/inventory_screen.cpp index 99890e33..fb985bd9 100644 --- a/src/ui/inventory_screen.cpp +++ b/src/ui/inventory_screen.cpp @@ -395,6 +395,13 @@ game::EquipSlot InventoryScreen::getEquipSlotForType(uint8_t inventoryType, game case 26: // Ranged return game::EquipSlot::RANGED; case 16: return game::EquipSlot::BACK; + case 18: { + for (int i = 0; i < game::Inventory::NUM_BAG_SLOTS; ++i) { + auto slot = static_cast(static_cast(game::EquipSlot::BAG1) + i); + if (inv.getEquipSlot(slot).empty()) return slot; + } + return game::EquipSlot::BAG1; + } case 19: return game::EquipSlot::TABARD; case 20: return game::EquipSlot::CHEST; // Robe default: return game::EquipSlot::NUM_SLOTS; @@ -518,40 +525,56 @@ void InventoryScreen::placeInBag(game::Inventory& inv, int bagIndex, int slotInd void InventoryScreen::placeInEquipment(game::Inventory& inv, game::EquipSlot slot) { if (!holdingItem) return; - if (gameHandler_) { - if (heldSource == HeldSource::BACKPACK && heldBackpackIndex >= 0) { - gameHandler_->autoEquipItemBySlot(heldBackpackIndex); - cancelPickup(inv); - return; - } - if (heldSource == HeldSource::BAG) { - gameHandler_->autoEquipItemInBag(heldBagIndex, heldBagSlotIndex); - cancelPickup(inv); - return; - } - if (heldSource == HeldSource::EQUIPMENT) { - cancelPickup(inv); - return; - } - } // Validate: check if the held item can go in this slot if (heldItem.inventoryType > 0) { - game::EquipSlot validSlot = getEquipSlotForType(heldItem.inventoryType, inv); - if (validSlot == game::EquipSlot::NUM_SLOTS) return; + bool valid = false; + if (heldItem.inventoryType == 18) { + valid = (slot >= game::EquipSlot::BAG1 && slot <= game::EquipSlot::BAG4); + } else { + game::EquipSlot validSlot = getEquipSlotForType(heldItem.inventoryType, inv); + if (validSlot == game::EquipSlot::NUM_SLOTS) return; - bool valid = (slot == validSlot); - if (!valid) { - if (heldItem.inventoryType == 11) - valid = (slot == game::EquipSlot::RING1 || slot == game::EquipSlot::RING2); - else if (heldItem.inventoryType == 12) - valid = (slot == game::EquipSlot::TRINKET1 || slot == game::EquipSlot::TRINKET2); + valid = (slot == validSlot); + if (!valid) { + if (heldItem.inventoryType == 11) + valid = (slot == game::EquipSlot::RING1 || slot == game::EquipSlot::RING2); + else if (heldItem.inventoryType == 12) + valid = (slot == game::EquipSlot::TRINKET1 || slot == game::EquipSlot::TRINKET2); + } } if (!valid) return; } else { return; } + if (gameHandler_) { + uint8_t dstBag = 0xFF; + uint8_t dstSlot = static_cast(slot); + uint8_t srcBag = 0xFF; + uint8_t srcSlot = 0; + if (heldSource == HeldSource::BACKPACK && heldBackpackIndex >= 0) { + srcSlot = static_cast(23 + heldBackpackIndex); + } else if (heldSource == HeldSource::BAG && heldBagIndex >= 0 && heldBagSlotIndex >= 0) { + srcBag = static_cast(19 + heldBagIndex); + srcSlot = static_cast(heldBagSlotIndex); + } else if (heldSource == HeldSource::EQUIPMENT && heldEquipSlot != game::EquipSlot::NUM_SLOTS) { + srcSlot = static_cast(heldEquipSlot); + } else { + cancelPickup(inv); + return; + } + + if (srcBag == dstBag && srcSlot == dstSlot) { + cancelPickup(inv); + return; + } + + gameHandler_->swapContainerItems(srcBag, srcSlot, dstBag, dstSlot); + cancelPickup(inv); + return; + } + const auto& target = inv.getEquipSlot(slot); if (target.empty()) { inv.setEquipSlot(slot, heldItem); @@ -657,6 +680,20 @@ void InventoryScreen::renderHeldItem() { } } +bool InventoryScreen::dropHeldItemToEquipSlot(game::Inventory& inv, game::EquipSlot slot) { + if (!holdingItem) return false; + placeInEquipment(inv, slot); + return !holdingItem; +} + +bool InventoryScreen::beginPickupFromEquipSlot(game::Inventory& inv, game::EquipSlot slot) { + if (holdingItem) return false; + const auto& eq = inv.getEquipSlot(slot); + if (eq.empty()) return false; + pickupFromEquipment(inv, slot); + return holdingItem; +} + // ============================================================ // Bags window (B key) — bottom of screen, no equipment panel // ============================================================ @@ -1392,12 +1429,16 @@ void InventoryScreen::renderItemSlot(game::Inventory& inventory, const game::Ite if (kind == SlotKind::BACKPACK && (backpackIndex >= 0 || isBagSlot)) { validDrop = true; } else if (kind == SlotKind::EQUIPMENT && heldItem.inventoryType > 0) { - game::EquipSlot validSlot = getEquipSlotForType(heldItem.inventoryType, inventory); - validDrop = (equipSlot == validSlot); - if (!validDrop && heldItem.inventoryType == 11) - validDrop = (equipSlot == game::EquipSlot::RING1 || equipSlot == game::EquipSlot::RING2); - if (!validDrop && heldItem.inventoryType == 12) - validDrop = (equipSlot == game::EquipSlot::TRINKET1 || equipSlot == game::EquipSlot::TRINKET2); + if (heldItem.inventoryType == 18) { + validDrop = (equipSlot >= game::EquipSlot::BAG1 && equipSlot <= game::EquipSlot::BAG4); + } else { + game::EquipSlot validSlot = getEquipSlotForType(heldItem.inventoryType, inventory); + validDrop = (equipSlot == validSlot); + if (!validDrop && heldItem.inventoryType == 11) + validDrop = (equipSlot == game::EquipSlot::RING1 || equipSlot == game::EquipSlot::RING2); + if (!validDrop && heldItem.inventoryType == 12) + validDrop = (equipSlot == game::EquipSlot::TRINKET1 || equipSlot == game::EquipSlot::TRINKET2); + } } }