From 078a48029127491416e4fa9627165f85030a9ac4 Mon Sep 17 00:00:00 2001 From: bash Date: Fri, 1 May 2026 14:50:12 +0200 Subject: [PATCH] feat(Core/Loot): Make bag space for incoming quest items --- src/Ai/Base/Actions/LootAction.cpp | 61 +++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/src/Ai/Base/Actions/LootAction.cpp b/src/Ai/Base/Actions/LootAction.cpp index 9cd515fdc..f225235a2 100644 --- a/src/Ai/Base/Actions/LootAction.cpp +++ b/src/Ai/Base/Actions/LootAction.cpp @@ -5,6 +5,8 @@ #include "LootAction.h" +#include + #include "Bag.h" #include "ChatHelper.h" #include "Event.h" @@ -379,6 +381,12 @@ bool StoreLootAction::Execute(Event event) // bot->GetSession()->HandleLootMoneyOpcode(packet); } + // one make-room destroy per loot packet — CanStoreNewItem after a junk + // destroy can still report full while CMSG_AUTOSTORE_LOOT_ITEM is + // queued, so a multi-quest-item packet would otherwise destroy more + // junk than necessary + bool destroyedThisPacket = false; + for (uint8 i = 0; i < items; ++i) { uint32 itemid; @@ -404,7 +412,9 @@ bool StoreLootAction::Execute(Event event) if (!proto) continue; - if (!botAI->HasActivePlayerMaster() && AI_VALUE(uint8, "bag space") > 80) + // bags >80%: skip non-stackable junk (quest items exempt) + if (!botAI->HasActivePlayerMaster() && AI_VALUE(uint8, "bag space") > 80 && + !bot->HasQuestForItem(itemid)) { uint32 maxStack = proto->GetMaxStackSize(); if (maxStack == 1) @@ -440,6 +450,55 @@ bool StoreLootAction::Execute(Event event) GuildTaskMgr::instance().CheckItemTask(itemid, itemcount, ref->GetSource(), bot); } + // bags full + quest item: make room by dropping cheapest junk + if (!destroyedThisPacket && bot->HasQuestForItem(itemid)) + { + ItemPosCountVec dest; + InventoryResult can = + bot->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, itemid, itemcount); + if (can == EQUIP_ERR_INVENTORY_FULL || can == EQUIP_ERR_BAG_FULL) + { + // picked by usage, not quality — high-level bots have no grays + Item* victim = nullptr; + uint32 minPrice = std::numeric_limits::max(); + auto consider = [&](uint8 bag, uint8 slot) + { + Item* it = bot->GetItemByPos(bag, slot); + if (!it) + return; + ItemTemplate const* tpl = it->GetTemplate(); + if (!tpl) + return; + if (bot->HasQuestForItem(tpl->ItemId)) + return; + ItemUsage usage = AI_VALUE2(ItemUsage, "item usage", tpl->ItemId); + if (usage != ITEM_USAGE_NONE && usage != ITEM_USAGE_VENDOR && + usage != ITEM_USAGE_BAD_EQUIP && usage != ITEM_USAGE_BROKEN_EQUIP) + return; + if (tpl->SellPrice < minPrice) + { + minPrice = tpl->SellPrice; + victim = it; + } + }; + for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; ++slot) + consider(INVENTORY_SLOT_BAG_0, slot); + for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag) + { + Bag* pBag = bot->GetBagByPos(bag); + if (!pBag) + continue; + for (uint32 slot = 0; slot < pBag->GetBagSize(); ++slot) + consider(bag, static_cast(slot)); + } + if (victim) + { + bot->DestroyItem(victim->GetBagSlot(), victim->GetSlot(), true); + destroyedThisPacket = true; + } + } + } + WorldPacket* packet = new WorldPacket(CMSG_AUTOSTORE_LOOT_ITEM, 1); *packet << itemindex; bot->GetSession()->QueuePacket(packet);