Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 103 additions & 3 deletions src/creatures/npcs/npc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<uint16_t, uint16_t> toSell;
uint32_t MAX_BATCH_SIZE = 10;
uint32_t processedCount = 0;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -559,6 +604,61 @@ void Npc::onPlayerSellItem(const std::shared_ptr<Player> &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<uint32_t>(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<uint64_t>(sellPrice) * static_cast<uint64_t>(willRemove);
uint32_t crystalCoins = static_cast<uint32_t>(prospectiveTotal / 10000);
uint32_t remainder = static_cast<uint32_t>(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<uint16_t>((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<uint64_t>(goldCoins) * goldWeight + static_cast<uint64_t>(platinumCoins) * platWeight + static_cast<uint64_t>(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()) {
Expand Down
Loading