Add item support for action bar with drag-from-inventory and key/click use

Allow picking up consumables from inventory and dropping them onto action bar
slots. Items display their icon or name, can be used via click or hotkey
(1-0,-,=), and cleared with right-click. Adds useItemById to find and use
items from backpack by item ID.
This commit is contained in:
Kelsi 2026-02-06 19:17:35 -08:00
parent 40c016ccdb
commit affb5f4f04
4 changed files with 62 additions and 1 deletions

View file

@ -368,6 +368,7 @@ public:
void sellItemBySlot(int backpackIndex);
void autoEquipItemBySlot(int backpackIndex);
void useItemBySlot(int backpackIndex);
void useItemById(uint32_t itemId);
bool isVendorWindowOpen() const { return vendorWindowOpen; }
const ListInventoryData& getVendorItems() const { return currentVendorItems; }
const ItemQueryResponseData* getItemInfo(uint32_t itemId) const {

View file

@ -134,6 +134,13 @@ private:
public:
static ImVec4 getQualityColor(game::ItemQuality quality);
/// Returns true if the user is currently holding an item (pickup cursor).
bool isHoldingItem() const { return holdingItem; }
/// Returns the item being held (only valid when isHoldingItem() is true).
const game::ItemDef& getHeldItem() const { return heldItem; }
/// Cancel the pickup, returning the item to its original slot.
void returnHeldItem(game::Inventory& inv) { cancelPickup(inv); }
};
} // namespace ui

View file

@ -4147,6 +4147,17 @@ void GameHandler::useItemBySlot(int backpackIndex) {
}
}
void GameHandler::useItemById(uint32_t itemId) {
if (itemId == 0) return;
for (int i = 0; i < inventory.getBackpackSize(); i++) {
const auto& slot = inventory.getBackpackSlot(i);
if (!slot.empty() && slot.item.itemId == itemId) {
useItemBySlot(i);
return;
}
}
}
void GameHandler::handleLootResponse(network::Packet& packet) {
if (!LootResponseParser::parse(packet, currentLoot)) return;
lootWindowOpen = true;

View file

@ -519,6 +519,8 @@ void GameScreen::processTargetInput(game::GameHandler& gameHandler) {
if (bar[i].type == game::ActionBarSlot::SPELL && bar[i].isReady()) {
uint64_t target = gameHandler.hasTarget() ? gameHandler.getTargetGuid() : 0;
gameHandler.castSpell(bar[i].id, target);
} else if (bar[i].type == game::ActionBarSlot::ITEM && bar[i].id != 0) {
gameHandler.useItemById(bar[i].id);
}
}
}
@ -1435,8 +1437,22 @@ void GameScreen::renderActionBar(game::GameHandler& gameHandler) {
// Try to get icon texture for this slot
GLuint iconTex = 0;
const game::ItemDef* barItemDef = nullptr;
if (slot.type == game::ActionBarSlot::SPELL && slot.id != 0) {
iconTex = getSpellIcon(slot.id, assetMgr);
} else if (slot.type == game::ActionBarSlot::ITEM && slot.id != 0) {
// Look up item in inventory for icon and name
auto& inv = gameHandler.getInventory();
for (int bi = 0; bi < inv.getBackpackSize(); bi++) {
const auto& bs = inv.getBackpackSlot(bi);
if (!bs.empty() && bs.item.itemId == slot.id) {
barItemDef = &bs.item;
break;
}
}
if (barItemDef && barItemDef->displayInfoId != 0) {
iconTex = inventoryScreen.getItemIcon(barItemDef->displayInfoId);
}
}
bool clicked = false;
@ -1468,6 +1484,10 @@ void GameScreen::renderActionBar(game::GameHandler& gameHandler) {
std::string spellName = getSpellName(slot.id);
if (spellName.size() > 6) spellName = spellName.substr(0, 6);
snprintf(label, sizeof(label), "%s", spellName.c_str());
} else if (slot.type == game::ActionBarSlot::ITEM && barItemDef) {
std::string itemName = barItemDef->name;
if (itemName.size() > 6) itemName = itemName.substr(0, 6);
snprintf(label, sizeof(label), "%s", itemName.c_str());
} else if (slot.type == game::ActionBarSlot::ITEM) {
snprintf(label, sizeof(label), "Item");
} else if (slot.type == game::ActionBarSlot::MACRO) {
@ -1480,19 +1500,41 @@ void GameScreen::renderActionBar(game::GameHandler& gameHandler) {
ImGui::PopStyleColor();
}
if (clicked) {
// Drop held item from inventory onto action bar
if (clicked && inventoryScreen.isHoldingItem()) {
const auto& held = inventoryScreen.getHeldItem();
gameHandler.setActionBarSlot(i, game::ActionBarSlot::ITEM, held.itemId);
inventoryScreen.returnHeldItem(gameHandler.getInventory());
} else if (clicked) {
if (slot.type == game::ActionBarSlot::SPELL && slot.isReady()) {
uint64_t target = gameHandler.hasTarget() ? gameHandler.getTargetGuid() : 0;
gameHandler.castSpell(slot.id, target);
} else if (slot.type == game::ActionBarSlot::ITEM && slot.id != 0) {
gameHandler.useItemById(slot.id);
}
}
// Right-click to clear slot
if (ImGui::IsItemClicked(ImGuiMouseButton_Right) && !slot.isEmpty()) {
gameHandler.setActionBarSlot(i, game::ActionBarSlot::EMPTY, 0);
}
// Tooltip
if (ImGui::IsItemHovered() && slot.type == game::ActionBarSlot::SPELL && slot.id != 0) {
std::string fullName = getSpellName(slot.id);
ImGui::BeginTooltip();
ImGui::Text("%s", fullName.c_str());
ImGui::TextDisabled("Spell ID: %u", slot.id);
ImGui::EndTooltip();
} else if (ImGui::IsItemHovered() && slot.type == game::ActionBarSlot::ITEM && slot.id != 0) {
ImGui::BeginTooltip();
if (barItemDef) {
ImGui::Text("%s", barItemDef->name.c_str());
} else {
ImGui::Text("Item #%u", slot.id);
}
ImGui::TextDisabled("(Right-click to remove)");
ImGui::EndTooltip();
}
// Cooldown overlay