fix(Core/Trade): correct packets, exchange checks, distance logic (#24710)

Co-authored-by: Wyrserth <wyrserth@protonmail.com>
Co-authored-by: Shauren <shauren.trinity@gmail.com>
Co-authored-by: Alan Deutscher <adeutscher@gmail.com>
Co-authored-by: Dehravor <dehravor@gmail.com>
Co-authored-by: robinsch <robinsch@users.noreply.github.com>
Co-authored-by: killerwife <killerwife@gmail.com>
This commit is contained in:
sogladev
2026-02-15 00:04:26 +01:00
committed by GitHub
parent 8748a34eee
commit b2a85da544
9 changed files with 321 additions and 188 deletions

View File

@@ -2186,7 +2186,7 @@ Rate.MissChanceMultiplier.OnlyAffectsPlayer = 0
# #
# LevelReq.Trade # LevelReq.Trade
# Description: Level requirement for characters to be able to trade. # Description: Level requirement for characters to be able to initiate a trade.
# Default: 1 # Default: 1
LevelReq.Trade = 1 LevelReq.Trade = 1

View File

@@ -1057,6 +1057,18 @@ struct EntryPointData
[[nodiscard]] bool HasTaxiPath() const { return taxiPath[0] && taxiPath[1]; } [[nodiscard]] bool HasTaxiPath() const { return taxiPath[0] && taxiPath[1]; }
}; };
struct TradeStatusInfo
{
TradeStatusInfo() = default;
TradeStatus Status{TRADE_STATUS_BUSY};
ObjectGuid TraderGuid{};
InventoryResult Result{EQUIP_ERR_OK};
bool IsTargetResult{false};
uint32 ItemLimitedByLimitCategory{0};
uint8 Slot{0};
};
struct PendingSpellCastRequest struct PendingSpellCastRequest
{ {
uint32 spellId; uint32 spellId;
@@ -1277,12 +1289,17 @@ public:
bool CanNoReagentCast(SpellInfo const* spellInfo) const; bool CanNoReagentCast(SpellInfo const* spellInfo) const;
[[nodiscard]] bool HasItemOrGemWithIdEquipped(uint32 item, uint32 count, uint8 except_slot = NULL_SLOT) const; [[nodiscard]] bool HasItemOrGemWithIdEquipped(uint32 item, uint32 count, uint8 except_slot = NULL_SLOT) const;
[[nodiscard]] bool HasItemOrGemWithLimitCategoryEquipped(uint32 limitCategory, uint32 count, uint8 except_slot = NULL_SLOT) const; [[nodiscard]] bool HasItemOrGemWithLimitCategoryEquipped(uint32 limitCategory, uint32 count, uint8 except_slot = NULL_SLOT) const;
InventoryResult CanTakeMoreSimilarItems(Item* pItem) const { return CanTakeMoreSimilarItems(pItem->GetEntry(), pItem->GetCount(), pItem); }
[[nodiscard]] InventoryResult CanTakeMoreSimilarItems(uint32 entry, uint32 count) const { return CanTakeMoreSimilarItems(entry, count, nullptr); } InventoryResult CanTakeMoreSimilarItems(Item* item, uint32* itemLimitedByLimitCategory = nullptr) const
{
return CanTakeMoreSimilarItems(item->GetEntry(), item->GetCount(), item, nullptr, itemLimitedByLimitCategory);
}
InventoryResult CanTakeMoreSimilarItems(uint32 entry, uint32 count, uint32* itemLimitedByLimitCategory = nullptr) const { return CanTakeMoreSimilarItems(entry, count, nullptr, nullptr, itemLimitedByLimitCategory); }
InventoryResult CanStoreNewItem(uint8 bag, uint8 slot, ItemPosCountVec& dest, uint32 item, uint32 count, uint32* no_space_count = nullptr) const InventoryResult CanStoreNewItem(uint8 bag, uint8 slot, ItemPosCountVec& dest, uint32 item, uint32 count, uint32* no_space_count = nullptr) const
{ {
return CanStoreItem(bag, slot, dest, item, count, nullptr, false, no_space_count); return CanStoreItem(bag, slot, dest, item, count, nullptr, false, no_space_count);
} }
InventoryResult CanStoreItem(uint8 bag, uint8 slot, ItemPosCountVec& dest, Item* pItem, bool swap = false) const InventoryResult CanStoreItem(uint8 bag, uint8 slot, ItemPosCountVec& dest, Item* pItem, bool swap = false) const
{ {
if (!pItem) if (!pItem)
@@ -1290,7 +1307,7 @@ public:
uint32 count = pItem->GetCount(); uint32 count = pItem->GetCount();
return CanStoreItem(bag, slot, dest, pItem->GetEntry(), count, pItem, swap, nullptr); return CanStoreItem(bag, slot, dest, pItem->GetEntry(), count, pItem, swap, nullptr);
} }
InventoryResult CanStoreItems(Item** pItem, int32 count) const; InventoryResult CanStoreItems(Item** items, int count, uint32* itemLimitedByLimitCategory) const;
InventoryResult CanEquipNewItem(uint8 slot, uint16& dest, uint32 item, bool swap) const; InventoryResult CanEquipNewItem(uint8 slot, uint16& dest, uint32 item, bool swap) const;
InventoryResult CanEquipItem(uint8 slot, uint16& dest, Item* pItem, bool swap, bool not_loading = true) const; InventoryResult CanEquipItem(uint8 slot, uint16& dest, Item* pItem, bool swap, bool not_loading = true) const;
@@ -1318,7 +1335,7 @@ public:
void UpdateLootAchievements(LootItem* item, Loot* loot); void UpdateLootAchievements(LootItem* item, Loot* loot);
void UpdateTitansGrip(); void UpdateTitansGrip();
InventoryResult CanTakeMoreSimilarItems(uint32 entry, uint32 count, Item* pItem, uint32* no_space_count = nullptr) const; InventoryResult CanTakeMoreSimilarItems(uint32 entry, uint32 count, Item* item, uint32* no_space_count = nullptr, uint32* itemLimitCategory = nullptr) const;
InventoryResult CanStoreItem(uint8 bag, uint8 slot, ItemPosCountVec& dest, uint32 entry, uint32 count, Item* pItem = nullptr, bool swap = false, uint32* no_space_count = nullptr) const; InventoryResult CanStoreItem(uint8 bag, uint8 slot, ItemPosCountVec& dest, uint32 entry, uint32 count, Item* pItem = nullptr, bool swap = false, uint32* no_space_count = nullptr) const;
void AddRefundReference(ObjectGuid itemGUID); void AddRefundReference(ObjectGuid itemGUID);

View File

@@ -799,7 +799,7 @@ bool Player::HasItemOrGemWithLimitCategoryEquipped(uint32 limitCategory, uint32
return false; return false;
} }
InventoryResult Player::CanTakeMoreSimilarItems(uint32 entry, uint32 count, Item* pItem, uint32* no_space_count) const InventoryResult Player::CanTakeMoreSimilarItems(uint32 entry, uint32 count, Item* item, uint32* no_space_count /*= nullptr*/, uint32* itemLimitedByLimitCategory /*= nullptr*/) const
{ {
ItemTemplate const* pProto = sObjectMgr->GetItemTemplate(entry); ItemTemplate const* pProto = sObjectMgr->GetItemTemplate(entry);
if (!pProto) if (!pProto)
@@ -809,13 +809,16 @@ InventoryResult Player::CanTakeMoreSimilarItems(uint32 entry, uint32 count, Item
return EQUIP_ERR_CANT_CARRY_MORE_OF_THIS; return EQUIP_ERR_CANT_CARRY_MORE_OF_THIS;
} }
if (item && item->m_lootGenerated)
return EQUIP_ERR_ALREADY_LOOTED;
// no maximum // no maximum
if ((pProto->MaxCount <= 0 && pProto->ItemLimitCategory == 0) || pProto->MaxCount == 2147483647) if ((pProto->MaxCount <= 0 && pProto->ItemLimitCategory == 0) || pProto->MaxCount == 2147483647)
return EQUIP_ERR_OK; return EQUIP_ERR_OK;
if (pProto->MaxCount > 0) if (pProto->MaxCount > 0)
{ {
uint32 curcount = GetItemCount(pProto->ItemId, true, pItem); uint32 curcount = GetItemCount(pProto->ItemId, true, item);
if (curcount + count > uint32(pProto->MaxCount)) if (curcount + count > uint32(pProto->MaxCount))
{ {
if (no_space_count) if (no_space_count)
@@ -837,11 +840,13 @@ InventoryResult Player::CanTakeMoreSimilarItems(uint32 entry, uint32 count, Item
if (limitEntry->mode == ITEM_LIMIT_CATEGORY_MODE_HAVE) if (limitEntry->mode == ITEM_LIMIT_CATEGORY_MODE_HAVE)
{ {
uint32 curcount = GetItemCountWithLimitCategory(pProto->ItemLimitCategory, pItem); uint32 curcount = GetItemCountWithLimitCategory(pProto->ItemLimitCategory, item);
if (curcount + count > uint32(limitEntry->maxCount)) if (curcount + count > uint32(limitEntry->maxCount))
{ {
if (no_space_count) if (no_space_count)
*no_space_count = count + curcount - limitEntry->maxCount; *no_space_count = count + curcount - limitEntry->maxCount;
if (itemLimitedByLimitCategory)
*itemLimitedByLimitCategory = pProto->ItemId;
return EQUIP_ERR_ITEM_MAX_LIMIT_CATEGORY_COUNT_EXCEEDED; return EQUIP_ERR_ITEM_MAX_LIMIT_CATEGORY_COUNT_EXCEEDED;
} }
} }
@@ -1119,7 +1124,7 @@ InventoryResult Player::CanStoreItem(uint8 bag, uint8 slot, ItemPosCountVec& des
if (pItem->IsBag() && pItem->IsNotEmptyBag()) if (pItem->IsBag() && pItem->IsNotEmptyBag())
return EQUIP_ERR_CAN_ONLY_DO_WITH_EMPTY_BAGS; return EQUIP_ERR_CAN_ONLY_DO_WITH_EMPTY_BAGS;
// Xinef: Removed next loot generated check // swapping/merging with currently looted item
if (pItem->GetGUID() == GetLootGUID()) if (pItem->GetGUID() == GetLootGUID())
{ {
if (no_space_count) if (no_space_count)
@@ -1538,79 +1543,102 @@ InventoryResult Player::CanStoreItem(uint8 bag, uint8 slot, ItemPosCountVec& des
} }
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
InventoryResult Player::CanStoreItems(Item** pItems, int32 count) const InventoryResult Player::CanStoreItems(Item** items, int count, uint32* itemLimitedByLimitCategory) const
{ {
Item* pItem2; Item* item2;
// fill space table // fill space tables, creating a mock-up of the player's inventory
int inv_slot_items[INVENTORY_SLOT_ITEM_END - INVENTORY_SLOT_ITEM_START];
int inv_bags[INVENTORY_SLOT_BAG_END - INVENTORY_SLOT_BAG_START][MAX_BAG_SIZE];
int inv_keys[KEYRING_SLOT_END - KEYRING_SLOT_START];
int inv_tokens[CURRENCYTOKEN_SLOT_END - CURRENCYTOKEN_SLOT_START];
memset(inv_slot_items, 0, sizeof(int) * (INVENTORY_SLOT_ITEM_END - INVENTORY_SLOT_ITEM_START)); // counts
memset(inv_bags, 0, sizeof(int) * (INVENTORY_SLOT_BAG_END - INVENTORY_SLOT_BAG_START) * MAX_BAG_SIZE); uint32 inventoryCounts[INVENTORY_SLOT_ITEM_END - INVENTORY_SLOT_ITEM_START] = {};
memset(inv_keys, 0, sizeof(int) * (KEYRING_SLOT_END - KEYRING_SLOT_START)); uint32 bagCounts[INVENTORY_SLOT_BAG_END - INVENTORY_SLOT_BAG_START][MAX_BAG_SIZE] = {};
memset(inv_tokens, 0, sizeof(int) * (CURRENCYTOKEN_SLOT_END - CURRENCYTOKEN_SLOT_START)); uint32 keyringCounts[KEYRING_SLOT_END - KEYRING_SLOT_START] = {};
uint32 currencyCounts[CURRENCYTOKEN_SLOT_END - CURRENCYTOKEN_SLOT_START] = {};
// Item pointers
Item* inventoryPointers[INVENTORY_SLOT_ITEM_END - INVENTORY_SLOT_ITEM_START] = {};
Item* bagPointers[INVENTORY_SLOT_BAG_END - INVENTORY_SLOT_BAG_START][MAX_BAG_SIZE] = {};
Item* keyringPointers[KEYRING_SLOT_END - KEYRING_SLOT_START] = {};
Item* currencyPointers[CURRENCYTOKEN_SLOT_END - CURRENCYTOKEN_SLOT_START] = {};
// filling inventory
for (uint8 i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; i++) for (uint8 i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; i++)
{ {
pItem2 = GetItemByPos(INVENTORY_SLOT_BAG_0, i); // build items in stock backpack
if (pItem2 && !pItem2->IsInTrade()) item2 = GetItemByPos(INVENTORY_SLOT_BAG_0, i);
inv_slot_items[i - INVENTORY_SLOT_ITEM_START] = pItem2->GetCount(); if (item2 && !item2->IsInTrade())
{
inventoryCounts[i - INVENTORY_SLOT_ITEM_START] = item2->GetCount();
inventoryPointers[i - INVENTORY_SLOT_ITEM_START] = item2;
}
} }
for (uint8 i = KEYRING_SLOT_START; i < KEYRING_SLOT_END; i++) for (uint8 i = KEYRING_SLOT_START; i < KEYRING_SLOT_END; i++)
{ {
pItem2 = GetItemByPos(INVENTORY_SLOT_BAG_0, i); // build items in key ring 'bag'
if (pItem2 && !pItem2->IsInTrade()) item2 = GetItemByPos(INVENTORY_SLOT_BAG_0, i);
inv_keys[i - KEYRING_SLOT_START] = pItem2->GetCount(); if (item2 && !item2->IsInTrade())
{
keyringCounts[i - KEYRING_SLOT_START] = item2->GetCount();
keyringPointers[i - KEYRING_SLOT_START] = item2;
}
} }
for (uint8 i = CURRENCYTOKEN_SLOT_START; i < CURRENCYTOKEN_SLOT_END; i++) for (uint8 i = CURRENCYTOKEN_SLOT_START; i < CURRENCYTOKEN_SLOT_END; i++)
{ {
pItem2 = GetItemByPos(INVENTORY_SLOT_BAG_0, i); // build items in currency 'bag'
if (pItem2 && !pItem2->IsInTrade()) item2 = GetItemByPos(INVENTORY_SLOT_BAG_0, i);
inv_tokens[i - CURRENCYTOKEN_SLOT_START] = pItem2->GetCount(); if (item2 && !item2->IsInTrade())
{
currencyCounts[i - CURRENCYTOKEN_SLOT_START] = item2->GetCount();
currencyPointers[i - CURRENCYTOKEN_SLOT_START] = item2;
}
} }
for (uint8 i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; i++) for (uint8 i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; i++)
if (Bag* pBag = GetBagByPos(i)) if (Bag* pBag = GetBagByPos(i))
for (uint32 j = 0; j < pBag->GetBagSize(); j++) for (uint32 j = 0; j < pBag->GetBagSize(); j++)
{ {
pItem2 = GetItemByPos(i, j); // build item counts in equippable bags
if (pItem2 && !pItem2->IsInTrade()) item2 = GetItemByPos(i, j);
inv_bags[i - INVENTORY_SLOT_BAG_START][j] = pItem2->GetCount(); if (item2 && !item2->IsInTrade())
{
bagCounts[i - INVENTORY_SLOT_BAG_START][j] = item2->GetCount();
bagPointers[i - INVENTORY_SLOT_BAG_START][j] = item2;
}
} }
// check free space for all items // check free space for all items that we wish to add
for (int k = 0; k < count; ++k) for (int k = 0; k < count; ++k)
{ {
Item* pItem = pItems[k]; // Incoming item
Item* item = items[k];
// no item // no item
if (!pItem) if (!item)
continue; continue;
LOG_DEBUG("entities.player.items", "STORAGE: CanStoreItems {}. item = {}, count = {}", k + 1, pItem->GetEntry(), pItem->GetCount()); uint32 remaining_count = item->GetCount();
ItemTemplate const* pProto = pItem->GetTemplate();
LOG_DEBUG("entities.player.items", "STORAGE: CanStoreItems {}. item = {}, count = {}", k + 1, item->GetEntry(), item->GetCount());
ItemTemplate const* pProto = item->GetTemplate();
// strange item // strange item
if (!pProto) if (!pProto)
return EQUIP_ERR_ITEM_NOT_FOUND; return EQUIP_ERR_ITEM_NOT_FOUND;
// Xinef: Removed next loot generated check /// swapping/merging with currently looted item
if (pItem->GetGUID() == GetLootGUID()) if (item->GetGUID() == GetLootGUID())
return EQUIP_ERR_ALREADY_LOOTED; return EQUIP_ERR_ALREADY_LOOTED;
// item it 'bind' // item it 'bind'
if (pItem->IsBindedNotWith(this)) if (item->IsBindedNotWith(this))
return EQUIP_ERR_DONT_OWN_THAT_ITEM; return EQUIP_ERR_DONT_OWN_THAT_ITEM;
ItemTemplate const* pBagProto; ItemTemplate const* pBagProto;
// item is 'one item only' // item is 'one item only'
InventoryResult res = CanTakeMoreSimilarItems(pItem); InventoryResult res = CanTakeMoreSimilarItems(item, itemLimitedByLimitCategory);
if (res != EQUIP_ERR_OK) if (res != EQUIP_ERR_OK)
return res; return res;
@@ -1621,40 +1649,58 @@ InventoryResult Player::CanStoreItems(Item** pItems, int32 count) const
for (uint8 t = KEYRING_SLOT_START; t < KEYRING_SLOT_END; ++t) for (uint8 t = KEYRING_SLOT_START; t < KEYRING_SLOT_END; ++t)
{ {
pItem2 = GetItemByPos(INVENTORY_SLOT_BAG_0, t); item2 = keyringPointers[t - KEYRING_SLOT_START];
if (pItem2 && pItem2->CanBeMergedPartlyWith(pProto) == EQUIP_ERR_OK && inv_keys[t - KEYRING_SLOT_START] + pItem->GetCount() <= pProto->GetMaxStackSize()) if (item2 && item2->CanBeMergedPartlyWith(pProto) == EQUIP_ERR_OK && keyringCounts[t - KEYRING_SLOT_START] < pProto->GetMaxStackSize())
{ {
inv_keys[t - KEYRING_SLOT_START] += pItem->GetCount(); keyringCounts[t - KEYRING_SLOT_START] += remaining_count;
b_found = true; remaining_count = keyringCounts[t - KEYRING_SLOT_START] < pProto->GetMaxStackSize() ? 0 : keyringCounts[t - KEYRING_SLOT_START] - pProto->GetMaxStackSize();
break;
b_found = remaining_count == 0;
// if no pieces of the stack remain, then stop checking keyring
if (b_found)
break;
} }
} }
if (b_found) if (b_found)
continue; continue;
for (int t = CURRENCYTOKEN_SLOT_START; t < CURRENCYTOKEN_SLOT_END; ++t) for (int t = CURRENCYTOKEN_SLOT_START; t < CURRENCYTOKEN_SLOT_END; ++t)
{ {
pItem2 = GetItemByPos(INVENTORY_SLOT_BAG_0, t); item2 = currencyPointers[t - CURRENCYTOKEN_SLOT_START];
if (pItem2 && pItem2->CanBeMergedPartlyWith(pProto) == EQUIP_ERR_OK && inv_tokens[t - CURRENCYTOKEN_SLOT_START] + pItem->GetCount() <= pProto->GetMaxStackSize()) if (item2 && item2->CanBeMergedPartlyWith(pProto) == EQUIP_ERR_OK && currencyCounts[t - CURRENCYTOKEN_SLOT_START] < pProto->GetMaxStackSize())
{ {
inv_tokens[t - CURRENCYTOKEN_SLOT_START] += pItem->GetCount(); currencyCounts[t - CURRENCYTOKEN_SLOT_START] += remaining_count;
b_found = true; remaining_count =
break; currencyCounts[t - CURRENCYTOKEN_SLOT_START] < pProto->GetMaxStackSize() ? 0 : currencyCounts[t - CURRENCYTOKEN_SLOT_START] - pProto->GetMaxStackSize();
b_found = remaining_count == 0;
// if no pieces of the stack remain, then stop checking currency 'bag'
if (b_found)
break;
} }
} }
if (b_found) if (b_found)
continue; continue;
for (int t = INVENTORY_SLOT_ITEM_START; t < INVENTORY_SLOT_ITEM_END; ++t) for (int t = INVENTORY_SLOT_ITEM_START; t < INVENTORY_SLOT_ITEM_END; ++t)
{ {
pItem2 = GetItemByPos(INVENTORY_SLOT_BAG_0, t); item2 = inventoryPointers[t - INVENTORY_SLOT_ITEM_START];
if (pItem2 && pItem2->CanBeMergedPartlyWith(pProto) == EQUIP_ERR_OK && inv_slot_items[t - INVENTORY_SLOT_ITEM_START] + pItem->GetCount() <= pProto->GetMaxStackSize()) if (item2 && item2->CanBeMergedPartlyWith(pProto) == EQUIP_ERR_OK && inventoryCounts[t - INVENTORY_SLOT_ITEM_START] < pProto->GetMaxStackSize())
{ {
inv_slot_items[t - INVENTORY_SLOT_ITEM_START] += pItem->GetCount(); inventoryCounts[t - INVENTORY_SLOT_ITEM_START] += remaining_count;
b_found = true; remaining_count =
break; inventoryCounts[t - INVENTORY_SLOT_ITEM_START] < pProto->GetMaxStackSize() ? 0 : inventoryCounts[t - INVENTORY_SLOT_ITEM_START] - pProto->GetMaxStackSize();
b_found = remaining_count == 0;
// if no pieces of the stack remain, then stop checking stock bag
if (b_found)
break;
} }
} }
if (b_found) if (b_found)
continue; continue;
@@ -1662,21 +1708,29 @@ InventoryResult Player::CanStoreItems(Item** pItems, int32 count) const
{ {
if (Bag* bag = GetBagByPos(t)) if (Bag* bag = GetBagByPos(t))
{ {
if (ItemCanGoIntoBag(pItem->GetTemplate(), bag->GetTemplate())) if (!ItemCanGoIntoBag(item->GetTemplate(), bag->GetTemplate()))
continue;
for (uint32 j = 0; j < bag->GetBagSize(); j++)
{ {
for (uint32 j = 0; j < bag->GetBagSize(); j++) item2 = bagPointers[t - INVENTORY_SLOT_BAG_START][j];
if (item2 && item2->CanBeMergedPartlyWith(pProto) == EQUIP_ERR_OK && bagCounts[t - INVENTORY_SLOT_BAG_START][j] < pProto->GetMaxStackSize())
{ {
pItem2 = GetItemByPos(t, j); // add count to stack so that later items in the list do not double-book
if (pItem2 && pItem2->CanBeMergedPartlyWith(pProto) == EQUIP_ERR_OK && inv_bags[t - INVENTORY_SLOT_BAG_START][j] + pItem->GetCount() <= pProto->GetMaxStackSize()) bagCounts[t - INVENTORY_SLOT_BAG_START][j] += remaining_count;
{ remaining_count =
inv_bags[t - INVENTORY_SLOT_BAG_START][j] += pItem->GetCount(); bagCounts[t - INVENTORY_SLOT_BAG_START][j] < pProto->GetMaxStackSize() ? 0 : bagCounts[t - INVENTORY_SLOT_BAG_START][j] - pProto->GetMaxStackSize();
b_found = true;
b_found = remaining_count == 0;
// if no pieces of the stack remain, then stop checking equippable bags
if (b_found)
break; break;
}
} }
} }
} }
} }
if (b_found) if (b_found)
continue; continue;
} }
@@ -1690,9 +1744,11 @@ InventoryResult Player::CanStoreItems(Item** pItems, int32 count) const
uint32 keyringSize = GetMaxKeyringSize(); uint32 keyringSize = GetMaxKeyringSize();
for (uint32 t = KEYRING_SLOT_START; t < KEYRING_SLOT_START + keyringSize; ++t) for (uint32 t = KEYRING_SLOT_START; t < KEYRING_SLOT_START + keyringSize; ++t)
{ {
if (inv_keys[t - KEYRING_SLOT_START] == 0) if (keyringCounts[t - KEYRING_SLOT_START] == 0)
{ {
inv_keys[t - KEYRING_SLOT_START] = 1; keyringCounts[t - KEYRING_SLOT_START] = remaining_count;
keyringPointers[t - KEYRING_SLOT_START] = item;
b_found = true; b_found = true;
break; break;
} }
@@ -1706,9 +1762,11 @@ InventoryResult Player::CanStoreItems(Item** pItems, int32 count) const
{ {
for (uint32 t = CURRENCYTOKEN_SLOT_START; t < CURRENCYTOKEN_SLOT_END; ++t) for (uint32 t = CURRENCYTOKEN_SLOT_START; t < CURRENCYTOKEN_SLOT_END; ++t)
{ {
if (inv_tokens[t - CURRENCYTOKEN_SLOT_START] == 0) if (currencyCounts[t - CURRENCYTOKEN_SLOT_START] == 0)
{ {
inv_tokens[t - CURRENCYTOKEN_SLOT_START] = 1; currencyCounts[t - CURRENCYTOKEN_SLOT_START] = remaining_count;
currencyPointers[t - CURRENCYTOKEN_SLOT_START] = item;
b_found = true; b_found = true;
break; break;
} }
@@ -1725,14 +1783,15 @@ InventoryResult Player::CanStoreItems(Item** pItems, int32 count) const
pBagProto = bag->GetTemplate(); pBagProto = bag->GetTemplate();
// not plain container check // not plain container check
if (pBagProto && (pBagProto->Class != ITEM_CLASS_CONTAINER || pBagProto->SubClass != ITEM_SUBCLASS_CONTAINER) && if (pBagProto && (pBagProto->Class != ITEM_CLASS_CONTAINER || pBagProto->SubClass != ITEM_SUBCLASS_CONTAINER) && ItemCanGoIntoBag(pProto, pBagProto))
ItemCanGoIntoBag(pProto, pBagProto))
{ {
for (uint32 j = 0; j < bag->GetBagSize(); j++) for (uint32 j = 0; j < bag->GetBagSize(); j++)
{ {
if (inv_bags[t - INVENTORY_SLOT_BAG_START][j] == 0) if (bagCounts[t - INVENTORY_SLOT_BAG_START][j] == 0)
{ {
inv_bags[t - INVENTORY_SLOT_BAG_START][j] = 1; bagCounts[t - INVENTORY_SLOT_BAG_START][j] = remaining_count;
bagPointers[t - INVENTORY_SLOT_BAG_START][j] = item;
b_found = true; b_found = true;
break; break;
} }
@@ -1740,6 +1799,7 @@ InventoryResult Player::CanStoreItems(Item** pItems, int32 count) const
} }
} }
} }
if (b_found) if (b_found)
continue; continue;
} }
@@ -1748,18 +1808,21 @@ InventoryResult Player::CanStoreItems(Item** pItems, int32 count) const
bool b_found = false; bool b_found = false;
for (int t = INVENTORY_SLOT_ITEM_START; t < INVENTORY_SLOT_ITEM_END; ++t) for (int t = INVENTORY_SLOT_ITEM_START; t < INVENTORY_SLOT_ITEM_END; ++t)
{ {
if (inv_slot_items[t - INVENTORY_SLOT_ITEM_START] == 0) if (inventoryCounts[t - INVENTORY_SLOT_ITEM_START] == 0)
{ {
inv_slot_items[t - INVENTORY_SLOT_ITEM_START] = 1; inventoryCounts[t - INVENTORY_SLOT_ITEM_START] = remaining_count;
inventoryPointers[t - INVENTORY_SLOT_ITEM_START] = item;
b_found = true; b_found = true;
break; break;
} }
} }
if (b_found) if (b_found)
continue; continue;
// search free slot in bags // search free slot in bags
for (int t = INVENTORY_SLOT_BAG_START; !b_found && t < INVENTORY_SLOT_BAG_END; ++t) for (uint8 t = INVENTORY_SLOT_BAG_START; !b_found && t < INVENTORY_SLOT_BAG_END; ++t)
{ {
if (Bag* bag = GetBagByPos(t)) if (Bag* bag = GetBagByPos(t))
{ {
@@ -1771,9 +1834,11 @@ InventoryResult Player::CanStoreItems(Item** pItems, int32 count) const
for (uint32 j = 0; j < bag->GetBagSize(); j++) for (uint32 j = 0; j < bag->GetBagSize(); j++)
{ {
if (inv_bags[t - INVENTORY_SLOT_BAG_START][j] == 0) if (bagCounts[t - INVENTORY_SLOT_BAG_START][j] == 0)
{ {
inv_bags[t - INVENTORY_SLOT_BAG_START][j] = 1; bagCounts[t - INVENTORY_SLOT_BAG_START][j] = remaining_count;
bagPointers[t - INVENTORY_SLOT_BAG_START][j] = item;
b_found = true; b_found = true;
break; break;
} }
@@ -1781,9 +1846,9 @@ InventoryResult Player::CanStoreItems(Item** pItems, int32 count) const
} }
} }
// no free slot found? // if no free slot found for all pieces of the item, then return an error
if (!b_found) if (!b_found)
return EQUIP_ERR_INVENTORY_FULL; return EQUIP_ERR_BAG_FULL;
} }
return EQUIP_ERR_OK; return EQUIP_ERR_OK;

View File

@@ -1177,9 +1177,6 @@ bool Player::UpdatePosition(float x, float y, float z, float orientation,
if (GetGroup()) if (GetGroup())
SetGroupUpdateFlag(GROUP_UPDATE_FLAG_POSITION); SetGroupUpdateFlag(GROUP_UPDATE_FLAG_POSITION);
if (GetTrader() && !IsWithinDistInMap(GetTrader(), INTERACTION_DISTANCE))
GetSession()->SendCancelTrade(TRADE_STATUS_TRADE_CANCELED);
CheckAreaExploreAndOutdoor(); CheckAreaExploreAndOutdoor();
return true; return true;

View File

@@ -20,12 +20,12 @@
TradeData* TradeData::GetTraderData() const TradeData* TradeData::GetTraderData() const
{ {
return m_trader->GetTradeData(); return _trader->GetTradeData();
} }
Item* TradeData::GetItem(TradeSlots slot) const Item* TradeData::GetItem(TradeSlots slot) const
{ {
return m_items[slot] ? m_player->GetItemByGuid(m_items[slot]) : nullptr; return m_items[slot] ? _player->GetItemByGuid(m_items[slot]) : nullptr;
} }
bool TradeData::HasItem(ObjectGuid itemGuid) const bool TradeData::HasItem(ObjectGuid itemGuid) const
@@ -48,7 +48,7 @@ TradeSlots TradeData::GetTradeSlotForItem(ObjectGuid itemGuid) const
Item* TradeData::GetSpellCastItem() const Item* TradeData::GetSpellCastItem() const
{ {
return m_spellCastItem ? m_player->GetItemByGuid(m_spellCastItem) : nullptr; return m_spellCastItem ? _player->GetItemByGuid(m_spellCastItem) : nullptr;
} }
void TradeData::SetItem(TradeSlots slot, Item* item) void TradeData::SetItem(TradeSlots slot, Item* item)
@@ -92,16 +92,19 @@ void TradeData::SetSpell(uint32 spell_id, Item* castItem /*= nullptr*/)
void TradeData::SetMoney(uint32 money) void TradeData::SetMoney(uint32 money)
{ {
if (m_money == money) if (_money == money)
return; return;
if (!m_player->HasEnoughMoney(money)) if (!_player->HasEnoughMoney(money))
{ {
m_player->GetSession()->SendTradeStatus(TRADE_STATUS_BUSY); TradeStatusInfo info;
info.Status = TRADE_STATUS_CLOSE_WINDOW;
info.Result = EQUIP_ERR_NOT_ENOUGH_MONEY;
_player->GetSession()->SendTradeStatus(info);
return; return;
} }
m_money = money; _money = money;
SetAccepted(false); SetAccepted(false);
GetTraderData()->SetAccepted(false); GetTraderData()->SetAccepted(false);
@@ -112,20 +115,22 @@ void TradeData::SetMoney(uint32 money)
void TradeData::Update(bool forTarget /*= true*/) void TradeData::Update(bool forTarget /*= true*/)
{ {
if (forTarget) if (forTarget)
m_trader->GetSession()->SendUpdateTrade(true); // player state for trader _trader->GetSession()->SendUpdateTrade(true); // player state for trader
else else
m_player->GetSession()->SendUpdateTrade(false); // player state for player _player->GetSession()->SendUpdateTrade(false); // player state for player
} }
void TradeData::SetAccepted(bool state, bool crosssend /*= false*/) void TradeData::SetAccepted(bool state, bool forTrader /*= false*/)
{ {
m_accepted = state; _accepted = state;
if (!state) if (!state)
{ {
if (crosssend) TradeStatusInfo info;
m_trader->GetSession()->SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE); info.Status = TRADE_STATUS_BACK_TO_TRADE;
if (forTrader)
_trader->GetSession()->SendTradeStatus(info);
else else
m_player->GetSession()->SendTradeStatus(TRADE_STATUS_BACK_TO_TRADE); _player->GetSession()->SendTradeStatus(info);
} }
} }

View File

@@ -36,9 +36,9 @@ class AC_GAME_API TradeData
{ {
public: // constructors public: // constructors
TradeData(Player* player, Player* trader) : TradeData(Player* player, Player* trader) :
m_player(player), m_trader(trader), m_accepted(false), m_acceptProccess(false), m_money(0), m_spell(0) { } _player(player), _trader(trader), _accepted(false), m_acceptProccess(false), _money(0), m_spell(0) { }
[[nodiscard]] Player* GetTrader() const { return m_trader; } [[nodiscard]] Player* GetTrader() const { return _trader; }
[[nodiscard]] TradeData* GetTraderData() const; [[nodiscard]] TradeData* GetTraderData() const;
[[nodiscard]] Item* GetItem(TradeSlots slot) const; [[nodiscard]] Item* GetItem(TradeSlots slot) const;
@@ -52,11 +52,11 @@ public: // constructors
[[nodiscard]] Item* GetSpellCastItem() const; [[nodiscard]] Item* GetSpellCastItem() const;
[[nodiscard]] bool HasSpellCastItem() const { return m_spellCastItem; } [[nodiscard]] bool HasSpellCastItem() const { return m_spellCastItem; }
[[nodiscard]] uint32 GetMoney() const { return m_money; } [[nodiscard]] uint32 GetMoney() const { return _money; }
void SetMoney(uint32 money); void SetMoney(uint32 money);
[[nodiscard]] bool IsAccepted() const { return m_accepted; } [[nodiscard]] bool IsAccepted() const { return _accepted; }
void SetAccepted(bool state, bool crosssend = false); void SetAccepted(bool state, bool forTrader = false);
[[nodiscard]] bool IsInAcceptProcess() const { return m_acceptProccess; } [[nodiscard]] bool IsInAcceptProcess() const { return m_acceptProccess; }
void SetInAcceptProcess(bool state) { m_acceptProccess = state; } void SetInAcceptProcess(bool state) { m_acceptProccess = state; }
@@ -65,13 +65,13 @@ private: // internal functions
void Update(bool for_trader = true); void Update(bool for_trader = true);
private: // fields private: // fields
Player* m_player; // Player who own of this TradeData Player* _player; // Player who own of this TradeData
Player* m_trader; // Player who trade with m_player Player* _trader; // Player who trade with m_player
bool m_accepted; // m_player press accept for trade list bool _accepted; // m_player press accept for trade list
bool m_acceptProccess; // one from player/trader press accept and this processed bool m_acceptProccess; // one from player/trader press accept and this processed
uint32 m_money; // m_player place money to trade uint32 _money; // m_player place money to trade
uint32 m_spell; // m_player apply spell to non-traded slot item uint32 m_spell; // m_player apply spell to non-traded slot item
ObjectGuid m_spellCastItem; // applied spell casted by item use ObjectGuid m_spellCastItem; // applied spell casted by item use

View File

@@ -30,38 +30,29 @@
#include "WorldPacket.h" #include "WorldPacket.h"
#include "WorldSession.h" #include "WorldSession.h"
void WorldSession::SendTradeStatus(TradeStatus status) void WorldSession::SendTradeStatus(TradeStatusInfo const& info)
{ {
WorldPacket data; WorldPacket data(SMSG_TRADE_STATUS, 13);
data << uint32(info.Status);
switch (status) switch (info.Status)
{ {
case TRADE_STATUS_BEGIN_TRADE: case TRADE_STATUS_BEGIN_TRADE:
data.Initialize(SMSG_TRADE_STATUS, 4 + 8); data << info.TraderGuid; // CGTradeInfo::m_tradingPlayer
data << uint32(status);
data << uint64(0);
break; break;
case TRADE_STATUS_OPEN_WINDOW: case TRADE_STATUS_OPEN_WINDOW:
data.Initialize(SMSG_TRADE_STATUS, 4 + 4); data << uint32(0); // CGTradeInfo::m_tradeID
data << uint32(status);
data << uint32(0); // added in 2.4.0
break; break;
case TRADE_STATUS_CLOSE_WINDOW: case TRADE_STATUS_CLOSE_WINDOW:
data.Initialize(SMSG_TRADE_STATUS, 4 + 4 + 1 + 4); data << uint32(info.Result); // InventoryResult
data << uint32(status); data << uint8(info.IsTargetResult); // bool isTargetError; used for: EQUIP_ERR_BAG_FULL, EQUIP_ERR_CANT_CARRY_MORE_OF_THIS, EQUIP_ERR_MISSING_REAGENT, EQUIP_ERR_ITEM_MAX_LIMIT_CATEGORY_COUNT_EXCEEDED
data << uint32(0); data << uint32(info.ItemLimitedByLimitCategory);// when result 84 - Item Id that was limited by ItemLimitCategory.dbc
data << uint8(0);
data << uint32(0);
break; break;
case TRADE_STATUS_ONLY_CONJURED: case TRADE_STATUS_WRONG_REALM:
case TRADE_STATUS_NOT_ELIGIBLE: case TRADE_STATUS_NOT_ON_TAPLIST:
data.Initialize(SMSG_TRADE_STATUS, 4 + 1); data << uint8(info.Slot); // Trade slot; -1 here clears CGTradeInfo::m_tradeMoney
data << uint32(status);
data << uint8(0);
break; break;
default: default:
data.Initialize(SMSG_TRADE_STATUS, 4);
data << uint32(status);
break; break;
} }
@@ -84,7 +75,7 @@ void WorldSession::SendUpdateTrade(bool trader_data /*= true*/)
WorldPacket data(SMSG_TRADE_STATUS_EXTENDED, 1 + 4 + 4 + 4 + 4 + 4 + 7 * (1 + 4 + 4 + 4 + 4 + 8 + 4 + 4 + 4 + 4 + 8 + 4 + 4 + 4 + 4 + 4 + 4)); WorldPacket data(SMSG_TRADE_STATUS_EXTENDED, 1 + 4 + 4 + 4 + 4 + 4 + 7 * (1 + 4 + 4 + 4 + 4 + 8 + 4 + 4 + 4 + 4 + 8 + 4 + 4 + 4 + 4 + 4 + 4));
data << uint8(trader_data); // 1 means traders data, 0 means own data << uint8(trader_data); // 1 means traders data, 0 means own
data << uint32(0); // added in 2.4.0, this value must be equal to value from TRADE_STATUS_OPEN_WINDOW status packet (different value for different players to block multiple trades?) data << uint32(0); // CGTradeInfo::m_tradeID
data << uint32(TRADE_SLOT_COUNT); // trade slots count/number?, = next field in most cases data << uint32(TRADE_SLOT_COUNT); // trade slots count/number?, = next field in most cases
data << uint32(TRADE_SLOT_COUNT); // trade slots count/number?, = prev field in most cases data << uint32(TRADE_SLOT_COUNT); // trade slots count/number?, = prev field in most cases
data << uint32(view_trade->GetMoney()); // trader gold data << uint32(view_trade->GetMoney()); // trader gold
@@ -253,17 +244,27 @@ void WorldSession::HandleAcceptTradeOpcode(WorldPacket& /*recvPacket*/)
if (!his_trade) if (!his_trade)
return; return;
Item* myItems[TRADE_SLOT_TRADED_COUNT] = { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr }; Item* myItems[TRADE_SLOT_TRADED_COUNT] = { };
Item* hisItems[TRADE_SLOT_TRADED_COUNT] = { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr }; Item* hisItems[TRADE_SLOT_TRADED_COUNT] = { };
bool myCanCompleteTrade = true, hisCanCompleteTrade = true;
// set before checks for propertly undo at problems (it already set in to client) // set before checks for propertly undo at problems (it already set in to client)
my_trade->SetAccepted(true); my_trade->SetAccepted(true);
TradeStatusInfo info;
if (!_player->IsWithinDistInMap(trader, TRADE_DISTANCE, false))
{
info.Status = TRADE_STATUS_TARGET_TO_FAR;
SendTradeStatus(info);
my_trade->SetAccepted(false);
return;
}
// not accept case incorrect money amount // not accept case incorrect money amount
if (!_player->HasEnoughMoney(my_trade->GetMoney())) if (!_player->HasEnoughMoney(my_trade->GetMoney()))
{ {
ChatHandler(this).SendNotification(LANG_NOT_ENOUGH_GOLD); info.Status = TRADE_STATUS_CLOSE_WINDOW;
info.Result = EQUIP_ERR_NOT_ENOUGH_MONEY;
SendTradeStatus(info);
my_trade->SetAccepted(false, true); my_trade->SetAccepted(false, true);
return; return;
} }
@@ -271,21 +272,27 @@ void WorldSession::HandleAcceptTradeOpcode(WorldPacket& /*recvPacket*/)
// not accept case incorrect money amount // not accept case incorrect money amount
if (!trader->HasEnoughMoney(his_trade->GetMoney())) if (!trader->HasEnoughMoney(his_trade->GetMoney()))
{ {
ChatHandler(trader->GetSession()).SendNotification(LANG_NOT_ENOUGH_GOLD); info.Status = TRADE_STATUS_CLOSE_WINDOW;
info.Result = EQUIP_ERR_NOT_ENOUGH_MONEY;
trader->GetSession()->SendTradeStatus(info);
his_trade->SetAccepted(false, true); his_trade->SetAccepted(false, true);
return; return;
} }
if (_player->GetMoney() >= uint32(MAX_MONEY_AMOUNT) - his_trade->GetMoney()) if (_player->GetMoney() >= MAX_MONEY_AMOUNT - his_trade->GetMoney())
{ {
_player->SendEquipError(EQUIP_ERR_TOO_MUCH_GOLD, nullptr, nullptr); info.Status = TRADE_STATUS_CLOSE_WINDOW;
info.Result = EQUIP_ERR_TOO_MUCH_GOLD;
SendTradeStatus(info);
my_trade->SetAccepted(false, true); my_trade->SetAccepted(false, true);
return; return;
} }
if (trader->GetMoney() >= uint32(MAX_MONEY_AMOUNT) - my_trade->GetMoney()) if (trader->GetMoney() >= MAX_MONEY_AMOUNT - my_trade->GetMoney())
{ {
trader->SendEquipError(EQUIP_ERR_TOO_MUCH_GOLD, nullptr, nullptr); info.Status = TRADE_STATUS_CLOSE_WINDOW;
info.Result = EQUIP_ERR_TOO_MUCH_GOLD;
trader->GetSession()->SendTradeStatus(info);
his_trade->SetAccepted(false, true); his_trade->SetAccepted(false, true);
return; return;
} }
@@ -297,14 +304,16 @@ void WorldSession::HandleAcceptTradeOpcode(WorldPacket& /*recvPacket*/)
{ {
if (!item->CanBeTraded(false, true)) if (!item->CanBeTraded(false, true))
{ {
SendTradeStatus(TRADE_STATUS_TRADE_CANCELED); info.Status = TRADE_STATUS_TRADE_CANCELED;
SendTradeStatus(info);
return; return;
} }
if (item->IsBindedNotWith(trader)) if (item->IsBindedNotWith(trader))
{ {
SendTradeStatus(TRADE_STATUS_NOT_ELIGIBLE); info.Status = TRADE_STATUS_CLOSE_WINDOW;
SendTradeStatus(TRADE_STATUS_CLOSE_WINDOW/*TRADE_STATUS_TRADE_CANCELED*/); info.Result = EQUIP_ERR_CANNOT_TRADE_THAT;
SendTradeStatus(info);
return; return;
} }
} }
@@ -313,7 +322,8 @@ void WorldSession::HandleAcceptTradeOpcode(WorldPacket& /*recvPacket*/)
{ {
if (!item->CanBeTraded(false, true)) if (!item->CanBeTraded(false, true))
{ {
SendTradeStatus(TRADE_STATUS_TRADE_CANCELED); info.Status = TRADE_STATUS_TRADE_CANCELED;
SendTradeStatus(info);
return; return;
} }
//if (item->IsBindedNotWith(_player)) // dont mark as invalid when his item isnt good (not exploitable because if item is invalid trade will fail anyway later on the same check) //if (item->IsBindedNotWith(_player)) // dont mark as invalid when his item isnt good (not exploitable because if item is invalid trade will fail anyway later on the same check)
@@ -408,33 +418,39 @@ void WorldSession::HandleAcceptTradeOpcode(WorldPacket& /*recvPacket*/)
} }
// inform partner client // inform partner client
trader->GetSession()->SendTradeStatus(TRADE_STATUS_TRADE_ACCEPT); info.Status = TRADE_STATUS_TRADE_ACCEPT;
trader->GetSession()->SendTradeStatus(info);
// test if item will fit in each inventory // test if item will fit in each inventory
hisCanCompleteTrade = (trader->CanStoreItems(myItems, TRADE_SLOT_TRADED_COUNT) == EQUIP_ERR_OK); TradeStatusInfo myCanCompleteInfo, hisCanCompleteInfo;
myCanCompleteTrade = (_player->CanStoreItems(hisItems, TRADE_SLOT_TRADED_COUNT) == EQUIP_ERR_OK); hisCanCompleteInfo.Result = trader->CanStoreItems(myItems, TRADE_SLOT_TRADED_COUNT, &hisCanCompleteInfo.ItemLimitedByLimitCategory);
myCanCompleteInfo.Result = _player->CanStoreItems(hisItems, TRADE_SLOT_TRADED_COUNT, &myCanCompleteInfo.ItemLimitedByLimitCategory);
clearAcceptTradeMode(myItems, hisItems); clearAcceptTradeMode(myItems, hisItems);
// in case of missing space report error // in case of missing space report error
if (!myCanCompleteTrade) if (myCanCompleteInfo.Result != EQUIP_ERR_OK)
{ {
clearAcceptTradeMode(my_trade, his_trade); clearAcceptTradeMode(my_trade, his_trade);
ChatHandler(this).SendNotification(LANG_NOT_FREE_TRADE_SLOTS); myCanCompleteInfo.Status = TRADE_STATUS_CLOSE_WINDOW;
ChatHandler(trader->GetSession()).SendNotification(LANG_NOT_PARTNER_FREE_TRADE_SLOTS); trader->GetSession()->SendTradeStatus(myCanCompleteInfo);
myCanCompleteInfo.IsTargetResult = true;
SendTradeStatus(myCanCompleteInfo);
my_trade->SetAccepted(false); my_trade->SetAccepted(false);
his_trade->SetAccepted(false); his_trade->SetAccepted(false);
delete my_spell; delete my_spell;
delete his_spell; delete his_spell;
return; return;
} }
else if (!hisCanCompleteTrade) else if (hisCanCompleteInfo.Result != EQUIP_ERR_OK)
{ {
clearAcceptTradeMode(my_trade, his_trade); clearAcceptTradeMode(my_trade, his_trade);
ChatHandler(this).SendNotification(LANG_NOT_PARTNER_FREE_TRADE_SLOTS); hisCanCompleteInfo.Status = TRADE_STATUS_CLOSE_WINDOW;
ChatHandler(trader->GetSession()).SendNotification(LANG_NOT_FREE_TRADE_SLOTS); SendTradeStatus(hisCanCompleteInfo);
hisCanCompleteInfo.IsTargetResult = true;
trader->GetSession()->SendTradeStatus(hisCanCompleteInfo);
my_trade->SetAccepted(false); my_trade->SetAccepted(false);
his_trade->SetAccepted(false); his_trade->SetAccepted(false);
delete my_spell; delete my_spell;
@@ -460,6 +476,7 @@ void WorldSession::HandleAcceptTradeOpcode(WorldPacket& /*recvPacket*/)
// execute trade: 2. store // execute trade: 2. store
moveItems(myItems, hisItems); moveItems(myItems, hisItems);
// logging money
if (my_trade->GetMoney() >= 10 * GOLD ) if (my_trade->GetMoney() >= 10 * GOLD )
{ {
CharacterDatabase.Execute("INSERT INTO log_money VALUES({}, {}, \"{}\", \"{}\", {}, \"{}\", {}, \"goods\", NOW(), {})", CharacterDatabase.Execute("INSERT INTO log_money VALUES({}, {}, \"{}\", \"{}\", {}, \"{}\", {}, \"goods\", NOW(), {})",
@@ -496,12 +513,14 @@ void WorldSession::HandleAcceptTradeOpcode(WorldPacket& /*recvPacket*/)
trader->SaveInventoryAndGoldToDB(trans); trader->SaveInventoryAndGoldToDB(trans);
CharacterDatabase.CommitTransaction(trans); CharacterDatabase.CommitTransaction(trans);
trader->GetSession()->SendTradeStatus(TRADE_STATUS_TRADE_COMPLETE); info.Status = TRADE_STATUS_TRADE_COMPLETE;
SendTradeStatus(TRADE_STATUS_TRADE_COMPLETE); trader->GetSession()->SendTradeStatus(info);
SendTradeStatus(info);
} }
else else
{ {
trader->GetSession()->SendTradeStatus(TRADE_STATUS_TRADE_ACCEPT); info.Status = TRADE_STATUS_TRADE_ACCEPT;
trader->GetSession()->SendTradeStatus(info);
} }
} }
@@ -516,12 +535,14 @@ void WorldSession::HandleUnacceptTradeOpcode(WorldPacket& /*recvPacket*/)
void WorldSession::HandleBeginTradeOpcode(WorldPacket& /*recvPacket*/) void WorldSession::HandleBeginTradeOpcode(WorldPacket& /*recvPacket*/)
{ {
TradeData* my_trade = _player->m_trade; TradeData* my_trade = _player->GetTradeData();
if (!my_trade) if (!my_trade)
return; return;
my_trade->GetTrader()->GetSession()->SendTradeStatus(TRADE_STATUS_OPEN_WINDOW); TradeStatusInfo info;
SendTradeStatus(TRADE_STATUS_OPEN_WINDOW); info.Status = TRADE_STATUS_OPEN_WINDOW;
my_trade->GetTrader()->GetSession()->SendTradeStatus(info);
SendTradeStatus(info);
} }
void WorldSession::SendCancelTrade(TradeStatus status) void WorldSession::SendCancelTrade(TradeStatus status)
@@ -529,7 +550,9 @@ void WorldSession::SendCancelTrade(TradeStatus status)
if (PlayerRecentlyLoggedOut() || PlayerLogout()) if (PlayerRecentlyLoggedOut() || PlayerLogout())
return; return;
SendTradeStatus(status); TradeStatusInfo info;
info.Status = status;
SendTradeStatus(info);
} }
void WorldSession::HandleCancelTradeOpcode(WorldPacket& /*recvPacket*/) void WorldSession::HandleCancelTradeOpcode(WorldPacket& /*recvPacket*/)
@@ -544,36 +567,43 @@ void WorldSession::HandleInitiateTradeOpcode(WorldPacket& recvPacket)
ObjectGuid ID; ObjectGuid ID;
recvPacket >> ID; recvPacket >> ID;
if (GetPlayer()->m_trade) if (GetPlayer()->GetTradeData())
return; return;
TradeStatusInfo info;
if (!GetPlayer()->IsAlive()) if (!GetPlayer()->IsAlive())
{ {
SendTradeStatus(TRADE_STATUS_YOU_DEAD); info.Status = TRADE_STATUS_YOU_DEAD;
SendTradeStatus(info);
return; return;
} }
if (GetPlayer()->HasUnitState(UNIT_STATE_STUNNED)) if (GetPlayer()->HasUnitState(UNIT_STATE_STUNNED))
{ {
SendTradeStatus(TRADE_STATUS_YOU_STUNNED); info.Status = TRADE_STATUS_YOU_STUNNED;
SendTradeStatus(info);
return; return;
} }
if (isLogingOut()) if (isLogingOut())
{ {
SendTradeStatus(TRADE_STATUS_YOU_LOGOUT); info.Status = TRADE_STATUS_YOU_LOGOUT;
SendTradeStatus(info);
return; return;
} }
if (GetPlayer()->IsInFlight()) if (GetPlayer()->IsInFlight())
{ {
SendTradeStatus(TRADE_STATUS_TARGET_TO_FAR); info.Status = TRADE_STATUS_TARGET_TO_FAR;
SendTradeStatus(info);
return; return;
} }
if (GetPlayer()->GetLevel() < sWorld->getIntConfig(CONFIG_TRADE_LEVEL_REQ)) if (GetPlayer()->GetLevel() < sWorld->getIntConfig(CONFIG_TRADE_LEVEL_REQ))
{ {
ChatHandler(this).SendNotification(LANG_TRADE_REQ, sWorld->getIntConfig(CONFIG_TRADE_LEVEL_REQ)); ChatHandler(this).SendNotification(LANG_TRADE_REQ, sWorld->getIntConfig(CONFIG_TRADE_LEVEL_REQ));
info.Status = TRADE_STATUS_CLOSE_WINDOW;
SendTradeStatus(info);
return; return;
} }
@@ -584,55 +614,57 @@ void WorldSession::HandleInitiateTradeOpcode(WorldPacket& recvPacket)
if (!pOther) if (!pOther)
{ {
SendTradeStatus(TRADE_STATUS_NO_TARGET); info.Status = TRADE_STATUS_NO_TARGET;
SendTradeStatus(info);
return; return;
} }
if (pOther == GetPlayer() || pOther->m_trade) if (pOther == GetPlayer() || pOther->m_trade)
{ {
SendTradeStatus(TRADE_STATUS_BUSY); info.Status = TRADE_STATUS_BUSY;
SendTradeStatus(info);
return; return;
} }
if (!pOther->IsAlive()) if (!pOther->IsAlive())
{ {
SendTradeStatus(TRADE_STATUS_TARGET_DEAD); info.Status = TRADE_STATUS_TARGET_DEAD;
SendTradeStatus(info);
return; return;
} }
if (pOther->IsInFlight()) if (pOther->IsInFlight())
{ {
SendTradeStatus(TRADE_STATUS_TARGET_TO_FAR); info.Status = TRADE_STATUS_TARGET_TO_FAR;
SendTradeStatus(info);
return; return;
} }
if (pOther->HasUnitState(UNIT_STATE_STUNNED)) if (pOther->HasUnitState(UNIT_STATE_STUNNED))
{ {
SendTradeStatus(TRADE_STATUS_TARGET_STUNNED); info.Status = TRADE_STATUS_TARGET_STUNNED;
SendTradeStatus(info);
return; return;
} }
if (pOther->GetSession()->isLogingOut()) if (pOther->GetSession()->isLogingOut())
{ {
SendTradeStatus(TRADE_STATUS_TARGET_LOGOUT); info.Status = TRADE_STATUS_TARGET_LOGOUT;
SendTradeStatus(info);
return; return;
} }
if (!sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_TRADE) && pOther->GetTeamId() != _player->GetTeamId()) if (!sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_TRADE) && pOther->GetTeamId() != _player->GetTeamId())
{ {
SendTradeStatus(TRADE_STATUS_WRONG_FACTION); info.Status = TRADE_STATUS_WRONG_FACTION;
SendTradeStatus(info);
return; return;
} }
if (!pOther->IsWithinDistInMap(_player, 10.0f, false)) if (!pOther->IsWithinDistInMap(_player, TRADE_DISTANCE, false))
{ {
SendTradeStatus(TRADE_STATUS_TARGET_TO_FAR); info.Status = TRADE_STATUS_TARGET_TO_FAR;
return; SendTradeStatus(info);
}
if (pOther->GetLevel() < sWorld->getIntConfig(CONFIG_TRADE_LEVEL_REQ))
{
ChatHandler(this).SendNotification(LANG_TRADE_OTHER_REQ, sWorld->getIntConfig(CONFIG_TRADE_LEVEL_REQ));
return; return;
} }
@@ -643,10 +675,13 @@ void WorldSession::HandleInitiateTradeOpcode(WorldPacket& recvPacket)
_player->m_trade = new TradeData(_player, pOther); _player->m_trade = new TradeData(_player, pOther);
pOther->m_trade = new TradeData(pOther, _player); pOther->m_trade = new TradeData(pOther, _player);
WorldPacket data(SMSG_TRADE_STATUS, 12); // WorldPacket data(SMSG_TRADE_STATUS, 12);
data << uint32(TRADE_STATUS_BEGIN_TRADE); // data << uint32(TRADE_STATUS_BEGIN_TRADE);
data << _player->GetGUID(); // data << _player->GetGUID();
pOther->SendDirectMessage(&data); // pOther->SendDirectMessage(&data);
info.Status = TRADE_STATUS_BEGIN_TRADE;
info.TraderGuid = _player->GetGUID();
pOther->GetSession()->SendTradeStatus(info);
} }
void WorldSession::HandleSetTradeGoldOpcode(WorldPacket& recvPacket) void WorldSession::HandleSetTradeGoldOpcode(WorldPacket& recvPacket)
@@ -676,10 +711,12 @@ void WorldSession::HandleSetTradeItemOpcode(WorldPacket& recvPacket)
if (!my_trade) if (!my_trade)
return; return;
TradeStatusInfo info;
// invalid slot number // invalid slot number
if (tradeSlot >= TRADE_SLOT_COUNT) if (tradeSlot >= TRADE_SLOT_COUNT)
{ {
SendTradeStatus(TRADE_STATUS_TRADE_CANCELED); info.Status = TRADE_STATUS_TRADE_CANCELED;
SendTradeStatus(info);
return; return;
} }
@@ -687,7 +724,8 @@ void WorldSession::HandleSetTradeItemOpcode(WorldPacket& recvPacket)
Item* item = _player->GetItemByPos(bag, slot); Item* item = _player->GetItemByPos(bag, slot);
if (!item || (tradeSlot != TRADE_SLOT_NONTRADED && !item->CanBeTraded(false, true))) if (!item || (tradeSlot != TRADE_SLOT_NONTRADED && !item->CanBeTraded(false, true)))
{ {
SendTradeStatus(TRADE_STATUS_TRADE_CANCELED); info.Status = TRADE_STATUS_TRADE_CANCELED;
SendTradeStatus(info);
return; return;
} }
@@ -697,7 +735,8 @@ void WorldSession::HandleSetTradeItemOpcode(WorldPacket& recvPacket)
if (my_trade->HasItem(iGUID)) if (my_trade->HasItem(iGUID))
{ {
// cheating attempt // cheating attempt
SendTradeStatus(TRADE_STATUS_TRADE_CANCELED); info.Status = TRADE_STATUS_TRADE_CANCELED;
SendTradeStatus(info);
return; return;
} }
@@ -706,7 +745,16 @@ void WorldSession::HandleSetTradeItemOpcode(WorldPacket& recvPacket)
{ {
// Do not send TRADE_STATUS_TRADE_CANCELED because it will cause double display of "Transaction canceled" notification // Do not send TRADE_STATUS_TRADE_CANCELED because it will cause double display of "Transaction canceled" notification
// On the trade initiator screen // On the trade initiator screen
SendTradeStatus(TRADE_STATUS_CLOSE_WINDOW); info.Status = TRADE_STATUS_CLOSE_WINDOW;
SendTradeStatus(info);
return;
}
if (tradeSlot != TRADE_SLOT_NONTRADED && item->IsBindedNotWith(my_trade->GetTrader()))
{
info.Status = TRADE_STATUS_NOT_ON_TAPLIST;
info.Slot = tradeSlot;
SendTradeStatus(info);
return; return;
} }

View File

@@ -57,6 +57,7 @@ struct AuctionEntry;
struct DeclinedName; struct DeclinedName;
struct ItemTemplate; struct ItemTemplate;
struct MovementInfo; struct MovementInfo;
struct TradeStatusInfo;
namespace lfg namespace lfg
{ {
@@ -510,7 +511,7 @@ public:
void SendBattleGroundList(ObjectGuid guid, BattlegroundTypeId bgTypeId = BATTLEGROUND_RB); void SendBattleGroundList(ObjectGuid guid, BattlegroundTypeId bgTypeId = BATTLEGROUND_RB);
void SendTradeStatus(TradeStatus status); void SendTradeStatus(TradeStatusInfo const& info);
void SendUpdateTrade(bool trader_data = true); void SendUpdateTrade(bool trader_data = true);
void SendCancelTrade(TradeStatus status); void SendCancelTrade(TradeStatus status);

View File

@@ -3812,7 +3812,7 @@ enum TradeStatus
TRADE_STATUS_NO_TARGET = 6, TRADE_STATUS_NO_TARGET = 6,
TRADE_STATUS_BACK_TO_TRADE = 7, TRADE_STATUS_BACK_TO_TRADE = 7,
TRADE_STATUS_TRADE_COMPLETE = 8, TRADE_STATUS_TRADE_COMPLETE = 8,
// 9? TRADE_STATUS_TRADE_REJECTED = 9,
TRADE_STATUS_TARGET_TO_FAR = 10, TRADE_STATUS_TARGET_TO_FAR = 10,
TRADE_STATUS_WRONG_FACTION = 11, TRADE_STATUS_WRONG_FACTION = 11,
TRADE_STATUS_CLOSE_WINDOW = 12, TRADE_STATUS_CLOSE_WINDOW = 12,
@@ -3825,8 +3825,8 @@ enum TradeStatus
TRADE_STATUS_YOU_LOGOUT = 19, TRADE_STATUS_YOU_LOGOUT = 19,
TRADE_STATUS_TARGET_LOGOUT = 20, TRADE_STATUS_TARGET_LOGOUT = 20,
TRADE_STATUS_TRIAL_ACCOUNT = 21, // Trial accounts can not perform that action TRADE_STATUS_TRIAL_ACCOUNT = 21, // Trial accounts can not perform that action
TRADE_STATUS_ONLY_CONJURED = 22, // You can only trade conjured items... (cross realm BG related). TRADE_STATUS_WRONG_REALM = 22, // You can only trade conjured items... (cross realm BG related).
TRADE_STATUS_NOT_ELIGIBLE = 23 // Related to trading soulbound loot items TRADE_STATUS_NOT_ON_TAPLIST = 23 // Related to trading soulbound loot items
}; };
enum XPColorChar : uint8 enum XPColorChar : uint8