Core/Items: Move selling items to separate function

This commit is contained in:
Shauren
2026-03-01 15:45:26 +01:00
parent 1db53689a4
commit 2847100a2d
4 changed files with 99 additions and 105 deletions

View File

@@ -23673,6 +23673,77 @@ bool Player::BuyItemFromVendorSlot(ObjectGuid vendorguid, uint32 vendorslot, uin
return crItem->maxcount != 0;
}
Optional<SellResult> Player::CanSellItemToVendor(Item const* item, uint32 amount) const
{
// prevent sell not owner item
if (GetGUID() != item->GetOwnerGUID())
return SELL_ERR_CANT_SELL_ITEM;
// prevent sell non empty bag by drag-and-drop at vendor's item list
if (item->IsNotEmptyBag())
return SELL_ERR_CANT_SELL_ITEM;
// prevent sell currently looted item
if (GetLootGUID() == item->GetGUID())
return SELL_ERR_CANT_SELL_ITEM;
// prevent sell more items that exist in stack (possible only not from client)
if (amount > item->GetCount())
return SELL_ERR_CANT_SELL_ITEM;
uint32 sellPrice = item->GetSellPrice(this);
if (sellPrice <= 0)
return SELL_ERR_CANT_SELL_ITEM;
uint64 money = uint64(sellPrice) * amount;
using BuybackStorageType = std::remove_cvref_t<decltype(m_activePlayerData->BuybackPrice[0])>;
if (money > std::numeric_limits<BuybackStorageType>::max()) // ensure sell price * amount doesn't overflow buyback price
return SELL_ERR_CANT_SELL_ITEM;
return { };
}
Optional<SellResult> Player::SellItemToVendor(Item* item, uint32 amount)
{
uint64 money = uint64(item->GetSellPrice(this)) * amount;
if (!ModifyMoney(money)) // ensure player doesn't exceed gold limit
return SELL_ERR_CANT_SELL_ITEM;
UpdateCriteria(CriteriaType::MoneyEarnedFromSales, money);
UpdateCriteria(CriteriaType::SellItemsToVendors, 1);
if (amount < item->GetCount()) // need split items
{
Item* pNewItem = item->CloneItem(amount, this);
if (!pNewItem)
{
TC_LOG_ERROR("network", "Player::SellItemToVendor - could not create clone of item {}; count = {}", item->GetEntry(), amount);
return SELL_ERR_CANT_SELL_ITEM;
}
item->SetCount(item->GetCount() - amount);
ItemRemovedQuestCheck(item->GetEntry(), amount);
if (IsInWorld())
item->SendUpdateToPlayer(this);
item->SetState(ITEM_CHANGED, this);
AddItemToBuyBackSlot(pNewItem);
if (IsInWorld())
pNewItem->SendUpdateToPlayer(this);
}
else
{
RemoveItem(item->GetBagSlot(), item->GetSlot(), true);
ItemRemovedQuestCheck(item->GetEntry(), item->GetCount());
RemoveItemFromUpdateQueueOf(item, this);
AddItemToBuyBackSlot(item);
}
return { };
}
uint32 Player::GetMaxPersonalArenaRatingRequirement(uint32 minarenaslot) const
{
// returns the maximal personal arena rating that can be used to purchase items requiring this condition

View File

@@ -1582,6 +1582,8 @@ class TC_GAME_API Player final : public Unit, public GridObject<Player>
bool IsUsingTwoHandedWeaponInOneHand() const;
void SendNewItem(Item* item, uint32 quantity, bool received, bool created, bool broadcast = false, uint32 dungeonEncounterId = 0);
bool BuyItemFromVendorSlot(ObjectGuid vendorguid, uint32 vendorslot, uint32 item, uint32 count, uint8 bag, uint8 slot);
Optional<SellResult> CanSellItemToVendor(Item const* item, uint32 amount) const;
Optional<SellResult> SellItemToVendor(Item* item, uint32 amount);
bool BuyCurrencyFromVendorSlot(ObjectGuid vendorGuid, uint32 vendorSlot, uint32 currency, uint32 count);
bool _StoreOrEquipNewItem(uint32 vendorslot, uint32 item, uint8 count, uint8 bag, uint8 slot, int64 price, ItemTemplate const* pProto, Creature* pVendor, VendorItem const* crItem, bool bStore);

View File

@@ -381,129 +381,50 @@ void WorldSession::HandleReadItem(WorldPackets::Item::ReadItem& readItem)
_player->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, nullptr, nullptr);
}
void WorldSession::HandleSellItemOpcode(WorldPackets::Item::SellItem& packet)
void WorldSession::HandleSellItemOpcode(WorldPackets::Item::SellItem const& sellItem)
{
TC_LOG_DEBUG("network", "WORLD: Received CMSG_SELL_ITEM: Vendor {}, Item {}, Amount: {}",
packet.VendorGUID.ToString(), packet.ItemGUID.ToString(), packet.Amount);
sellItem.VendorGUID, sellItem.ItemGUID, sellItem.Amount);
if (packet.ItemGUID.IsEmpty())
return;
Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(packet.VendorGUID, UNIT_NPC_FLAG_VENDOR, UNIT_NPC_FLAG_2_NONE);
Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(sellItem.VendorGUID, UNIT_NPC_FLAG_VENDOR, UNIT_NPC_FLAG_2_NONE);
if (!creature)
{
TC_LOG_DEBUG("network", "WORLD: HandleSellItemOpcode - {} not found or you can not interact with him.", packet.VendorGUID.ToString());
_player->SendSellError(SELL_ERR_CANT_FIND_VENDOR, nullptr, packet.ItemGUID);
TC_LOG_DEBUG("network", "WORLD: HandleSellItemOpcode - {} not found or you can not interact with him.", sellItem.VendorGUID);
_player->SendSellError(SELL_ERR_CANT_FIND_VENDOR, nullptr, sellItem.ItemGUID);
return;
}
if ((creature->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_SELL_VENDOR) != 0)
{
_player->SendSellError(SELL_ERR_CANT_SELL_TO_THIS_MERCHANT, creature, packet.ItemGUID);
_player->SendSellError(SELL_ERR_CANT_SELL_TO_THIS_MERCHANT, creature, sellItem.ItemGUID);
return;
}
Item* pItem = _player->GetItemByGuid(sellItem.ItemGUID);
if (!pItem)
{
_player->SendSellError(SELL_ERR_CANT_FIND_ITEM, creature, sellItem.ItemGUID);
return;
}
// prevent selling item for sellprice when the item is still refundable
// this probably happens when right clicking a refundable item, the client sends both
// CMSG_SELL_ITEM and CMSG_REFUND_ITEM (unverified)
if (pItem->IsRefundable())
return;
// remove fake death
if (GetPlayer()->HasUnitState(UNIT_STATE_DIED))
GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH);
Item* pItem = _player->GetItemByGuid(packet.ItemGUID);
if (pItem)
{
// prevent sell not owner item
if (_player->GetGUID() != pItem->GetOwnerGUID())
{
_player->SendSellError(SELL_ERR_CANT_SELL_ITEM, creature, packet.ItemGUID);
return;
}
uint32 amount = sellItem.Amount ? sellItem.Amount : pItem->GetCount();
// prevent sell non empty bag by drag-and-drop at vendor's item list
if (pItem->IsNotEmptyBag())
{
_player->SendSellError(SELL_ERR_CANT_SELL_ITEM, creature, packet.ItemGUID);
return;
}
Optional<SellResult> sellResult = _player->CanSellItemToVendor(pItem, amount);
if (!sellResult)
sellResult = _player->SellItemToVendor(pItem, amount);
// prevent sell currently looted item
if (_player->GetLootGUID() == pItem->GetGUID())
{
_player->SendSellError(SELL_ERR_CANT_SELL_ITEM, creature, packet.ItemGUID);
return;
}
// prevent selling item for sellprice when the item is still refundable
// this probably happens when right clicking a refundable item, the client sends both
// CMSG_SELL_ITEM and CMSG_REFUND_ITEM (unverified)
if (pItem->IsRefundable())
return; // Therefore, no feedback to client
// special case at auto sell (sell all)
if (packet.Amount == 0)
packet.Amount = pItem->GetCount();
else
{
// prevent sell more items that exist in stack (possible only not from client)
if (packet.Amount > pItem->GetCount())
{
_player->SendSellError(SELL_ERR_CANT_SELL_ITEM, creature, packet.ItemGUID);
return;
}
}
if (uint32 sellPrice = pItem->GetSellPrice(_player); sellPrice > 0)
{
uint64 money = uint64(sellPrice) * packet.Amount;
using BuybackStorageType = std::remove_cvref_t<decltype(_player->m_activePlayerData->BuybackPrice[0])>;
if (money > std::numeric_limits<BuybackStorageType>::max()) // ensure sell price * amount doesn't overflow buyback price
{
_player->SendSellError(SELL_ERR_CANT_SELL_ITEM, creature, packet.ItemGUID);
return;
}
if (!_player->ModifyMoney(money)) // ensure player doesn't exceed gold limit
{
_player->SendSellError(SELL_ERR_CANT_SELL_ITEM, creature, packet.ItemGUID);
return;
}
_player->UpdateCriteria(CriteriaType::MoneyEarnedFromSales, money);
_player->UpdateCriteria(CriteriaType::SellItemsToVendors, 1);
if (packet.Amount < pItem->GetCount()) // need split items
{
Item* pNewItem = pItem->CloneItem(packet.Amount, _player);
if (!pNewItem)
{
TC_LOG_ERROR("network", "WORLD: HandleSellItemOpcode - could not create clone of item {}; count = {}", pItem->GetEntry(), packet.Amount);
_player->SendSellError(SELL_ERR_CANT_SELL_ITEM, creature, packet.ItemGUID);
return;
}
pItem->SetCount(pItem->GetCount() - packet.Amount);
_player->ItemRemovedQuestCheck(pItem->GetEntry(), packet.Amount);
if (_player->IsInWorld())
pItem->SendUpdateToPlayer(_player);
pItem->SetState(ITEM_CHANGED, _player);
_player->AddItemToBuyBackSlot(pNewItem);
if (_player->IsInWorld())
pNewItem->SendUpdateToPlayer(_player);
}
else
{
_player->RemoveItem(pItem->GetBagSlot(), pItem->GetSlot(), true);
_player->ItemRemovedQuestCheck(pItem->GetEntry(), pItem->GetCount());
RemoveItemFromUpdateQueueOf(pItem, _player);
_player->AddItemToBuyBackSlot(pItem);
}
}
else
_player->SendSellError(SELL_ERR_CANT_SELL_ITEM, creature, packet.ItemGUID);
return;
}
_player->SendSellError(SELL_ERR_CANT_FIND_ITEM, creature, packet.ItemGUID);
return;
if (sellResult)
_player->SendSellError(*sellResult, creature, sellItem.ItemGUID);
}
void WorldSession::HandleBuybackItem(WorldPackets::Item::BuyBackItem& packet)

View File

@@ -1535,7 +1535,7 @@ class TC_GAME_API WorldSession
void HandleSwapInvItemOpcode(WorldPackets::Item::SwapInvItem& swapInvItem);
void HandleDestroyItemOpcode(WorldPackets::Item::DestroyItem& destroyItem);
void HandleAutoEquipItemOpcode(WorldPackets::Item::AutoEquipItem& autoEquipItem);
void HandleSellItemOpcode(WorldPackets::Item::SellItem& packet);
void HandleSellItemOpcode(WorldPackets::Item::SellItem const& sellItem);
void HandleBuyItemOpcode(WorldPackets::Item::BuyItem& packet);
void HandleListInventoryOpcode(WorldPackets::NPC::Hello& packet);
void HandleAutoStoreBagItemOpcode(WorldPackets::Item::AutoStoreBagItem& packet);