diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 36d3fe09fc..67918589a6 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -23673,6 +23673,77 @@ bool Player::BuyItemFromVendorSlot(ObjectGuid vendorguid, uint32 vendorslot, uin return crItem->maxcount != 0; } +Optional 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_tBuybackPrice[0])>; + if (money > std::numeric_limits::max()) // ensure sell price * amount doesn't overflow buyback price + return SELL_ERR_CANT_SELL_ITEM; + + return { }; +} + +Optional 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 diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index d7a1412499..94bcfa1991 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -1582,6 +1582,8 @@ class TC_GAME_API Player final : public Unit, public GridObject 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 CanSellItemToVendor(Item const* item, uint32 amount) const; + Optional 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); diff --git a/src/server/game/Handlers/ItemHandler.cpp b/src/server/game/Handlers/ItemHandler.cpp index 329849c5e3..daec396e03 100644 --- a/src/server/game/Handlers/ItemHandler.cpp +++ b/src/server/game/Handlers/ItemHandler.cpp @@ -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 = _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_tm_activePlayerData->BuybackPrice[0])>; - if (money > std::numeric_limits::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) diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h index cef8e827f5..5aa394ad93 100644 --- a/src/server/game/Server/WorldSession.h +++ b/src/server/game/Server/WorldSession.h @@ -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);