diff --git a/src/creatures/npcs/npc.cpp b/src/creatures/npcs/npc.cpp index 1ff6f34d5..20e352da0 100644 --- a/src/creatures/npcs/npc.cpp +++ b/src/creatures/npcs/npc.cpp @@ -458,6 +458,39 @@ void Npc::onPlayerSellAllLoot(uint32_t playerId, uint16_t itemId, bool ignore, u const auto preSize = container->size(); const uint64_t preTotal = totalPrice; + bool hasSellable = false; + const auto &shopVector = getShopItemVector(player->getGUID()); + for (ContainerIterator it = container->iterator(); it.hasNext(); it.advance()) { + const auto &item = *it; + if (!item) { + continue; + } + uint32_t sellPriceCandidate = 0; + const ItemType &itemType = Item::items[item->getID()]; + for (const ShopBlock &shopBlock : shopVector) { + if (itemType.id == shopBlock.itemId && shopBlock.itemSellPrice != 0) { + sellPriceCandidate = shopBlock.itemSellPrice; + break; + } + } + if (sellPriceCandidate == 0) { + continue; + } + if (item->getTier() > 0 || item->hasImbuements()) { + continue; + } + if (const auto &child = item->getContainer()) { + if (child->size() > 0) { + continue; + } + } + if (!item->hasMarketAttributes()) { + continue; + } + hasSellable = true; + break; + } + phmap::flat_hash_map toSell; uint32_t MAX_BATCH_SIZE = 10; uint32_t processedCount = 0; @@ -516,10 +549,22 @@ void Npc::onPlayerSellAllLoot(uint32_t playerId, uint16_t itemId, bool ignore, u auto ss = std::stringstream(); if (!madeProgress) { if (totalPrice == 0) { - ss << "You have no sellable items in your loot pouch."; - player->sendTextMessage(MESSAGE_FAILURE, ss.str()); + if (preSize == 0) { + ss << "You have no items in your loot pouch."; + player->sendTextMessage(MESSAGE_FAILURE, ss.str()); + } else if (!hasSellable) { + ss << "You have no sellable items in your loot pouch."; + player->sendTextMessage(MESSAGE_FAILURE, ss.str()); + } else { + ss << "You don't have enough space. Free up space in your bag."; + player->sendTextMessage(MESSAGE_FAILURE, ss.str()); + } } else { - ss << "Finished selling. Some items in your loot pouch could not be sold."; + ss << "Sale stopped. Some items in your loot bag could not be sold. Make sure you have enough space in your bag."; + player->sendTextMessage(MESSAGE_ADMINISTRATOR, ss.str()); + ss.str(""); + ss.clear(); + ss << "You sold all of the items from your loot pouch for " << totalPrice << " gold."; player->sendTextMessage(MESSAGE_LOOK, ss.str()); } } else { @@ -559,6 +604,61 @@ void Npc::onPlayerSellItem(const std::shared_ptr &player, uint16_t itemI return; } + // Pre-check: compute how many items can be sold in this call (eligible) without removing yet + uint32_t eligibleCount = 0; + for (const auto &item : player->getInventoryItemsFromId(itemId, ignore)) { + if (!item || item->getTier() > 0 || item->hasImbuements()) { + continue; + } + if (const auto &container = item->getContainer()) { + if (container->size() > 0) { + continue; + } + } + if (parent && item->getParent() != parent) { + continue; + } + if (!item->hasMarketAttributes()) { + continue; + } + eligibleCount += item->getItemCount(); + if (eligibleCount >= amount) { + break; + } + } + + const uint32_t willRemove = std::min(amount, eligibleCount); + if (willRemove == 0) { + return; + } + + // Capacity and backpack space check for gold payouts (when not using autobank) + if (getCurrency() == ITEM_GOLD_COIN && !g_configManager().getBoolean(AUTOBANK)) { + const uint64_t prospectiveTotal = static_cast(sellPrice) * static_cast(willRemove); + uint32_t crystalCoins = static_cast(prospectiveTotal / 10000); + uint32_t remainder = static_cast(prospectiveTotal % 10000); + uint32_t platinumCoins = remainder / 100; + uint32_t goldCoins = remainder % 100; + + // Number of stacks that will be created (each stack up to 100) + auto stacksNeeded = static_cast((crystalCoins + 99) / 100 + (platinumCoins + 99) / 100 + (goldCoins + 99) / 100); + const uint16_t freeSlots = player->getFreeBackpackSlots(); + if (stacksNeeded > 0 && freeSlots < stacksNeeded) { + player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM); + return; + } + + // Capacity check (approximate by coin unit weights) + const uint32_t goldWeight = Item::items[ITEM_GOLD_COIN].weight; + const uint32_t platWeight = Item::items[ITEM_PLATINUM_COIN].weight; + const uint32_t crysWeight = Item::items[ITEM_CRYSTAL_COIN].weight; + const uint64_t totalWeight = static_cast(goldCoins) * goldWeight + static_cast(platinumCoins) * platWeight + static_cast(crystalCoins) * crysWeight; + if (player->getFreeCapacity() < totalWeight) { + player->sendCancelMessage(RETURNVALUE_NOTENOUGHCAPACITY); + return; + } + } + auto toRemove = amount; for (const auto &item : player->getInventoryItemsFromId(itemId, ignore)) { if (!item || item->getTier() > 0 || item->hasImbuements()) {