diff --git a/src/server/game/Entities/Player/PlayerStorage.cpp b/src/server/game/Entities/Player/PlayerStorage.cpp index ea1d6fedec..c0e596e9c7 100644 --- a/src/server/game/Entities/Player/PlayerStorage.cpp +++ b/src/server/game/Entities/Player/PlayerStorage.cpp @@ -6209,6 +6209,32 @@ Item* Player::_LoadMailedItem(ObjectGuid const& playerGuid, Player* player, uint return nullptr; } + // Rehydrate looters for BoP-tradeable mail items; only the LFG mail path writes this flag, gated on the same config. + if (item->IsBOPTradable() && sWorld->getBoolConfig(CONFIG_SET_BOP_ITEM_TRADEABLE)) + { + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_ITEM_BOP_TRADE); + stmt->SetData(0, item->GetGUID().GetCounter()); + + if (PreparedQueryResult result = CharacterDatabase.Query(stmt)) + { + AllowedLooterSet looters; + for (std::string_view guidStr : Acore::Tokenize((*result)[0].Get(), ' ', false)) + { + if (Optional guid = Acore::StringTo(guidStr)) + looters.insert(ObjectGuid::Create(*guid)); + else + LOG_WARN("entities.player.loading", "Player::_LoadMailedItem: invalid item_soulbound_trade_data GUID '{}' for item {}. Skipped.", guidStr, item->GetGUID().ToString()); + } + + if (looters.size() > 1 && proto->GetMaxStackSize() == 1 && item->IsSoulBound()) + item->SetSoulboundTradeable(looters); + else + item->RemoveFlag(ITEM_FIELD_FLAGS, ITEM_FIELD_FLAG_BOP_TRADEABLE); + } + else + item->RemoveFlag(ITEM_FIELD_FLAGS, ITEM_FIELD_FLAG_BOP_TRADEABLE); + } + if (mail) { mail->AddItem(itemGuid, itemEntry); diff --git a/src/server/game/Groups/Group.cpp b/src/server/game/Groups/Group.cpp index 3a4e5a2fbb..0832f7ee21 100644 --- a/src/server/game/Groups/Group.cpp +++ b/src/server/game/Groups/Group.cpp @@ -63,6 +63,40 @@ Loot* Roll::getLoot() return getTarget(); } +static void SendRollWonItemViaMail(Player* player, LootItem const* lootItem, uint32 itemId) +{ + Item* mailItem = Item::CreateItem(itemId, lootItem->count, player, false, lootItem->randomPropertyId); + if (!mailItem) + return; + + AllowedLooterSet looters = lootItem->GetAllowedLooters(); + ItemTemplate const* proto = mailItem->GetTemplate(); + // Preserve the 2-hour group trade window the item would have had if stored directly. + if (looters.size() > 1 && proto->GetMaxStackSize() == 1 && + (proto->Bonding == BIND_WHEN_PICKED_UP || proto->Bonding == BIND_QUEST_ITEM) && + sWorld->getBoolConfig(CONFIG_SET_BOP_ITEM_TRADEABLE)) + { + mailItem->SetBinding(true); + mailItem->SetSoulboundTradeable(looters); + mailItem->SetUInt32Value(ITEM_FIELD_CREATE_PLAYED_TIME, player->GetTotalPlayedTime()); + + std::string lootersStr; + for (ObjectGuid const& guid : looters) + { + if (!lootersStr.empty()) + lootersStr += ' '; + lootersStr += std::to_string(guid.GetCounter()); + } + + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_ITEM_BOP_TRADE); + stmt->SetData(0, mailItem->GetGUID().GetCounter()); + stmt->SetData(1, lootersStr); + CharacterDatabase.Execute(stmt); + } + + player->SendItemRetrievalMail(mailItem); +} + Group::Group() : m_leaderName(""), m_groupType(GROUPTYPE_NORMAL), m_dungeonDifficulty(DUNGEON_DIFFICULTY_NORMAL), m_raidDifficulty(RAID_DIFFICULTY_10MAN_NORMAL), m_bfGroup(nullptr), m_bgGroup(nullptr), m_lootMethod(FREE_FOR_ALL), m_lootThreshold(ITEM_QUALITY_UNCOMMON), @@ -1497,8 +1531,7 @@ void Group::CountTheRoll(Rolls::iterator rollI, Map* allowedMap) roll->getLoot()->NotifyItemRemoved(roll->itemSlot); roll->getLoot()->unlootedCount--; player->SendEquipError(msg, nullptr, nullptr, roll->itemid); - if (Item* mailItem = Item::CreateItem(roll->itemid, item->count, player, false, item->randomPropertyId)) - player->SendItemRetrievalMail(mailItem); + SendRollWonItemViaMail(player, item, roll->itemid); } else { @@ -1580,8 +1613,7 @@ void Group::CountTheRoll(Rolls::iterator rollI, Map* allowedMap) roll->getLoot()->NotifyItemRemoved(roll->itemSlot); roll->getLoot()->unlootedCount--; player->SendEquipError(msg, nullptr, nullptr, roll->itemid); - if (Item* mailItem = Item::CreateItem(roll->itemid, item->count, player, false, item->randomPropertyId)) - player->SendItemRetrievalMail(mailItem); + SendRollWonItemViaMail(player, item, roll->itemid); } else {