mirror of
https://github.com/TrinityCore/TrinityCore.git
synced 2026-01-15 23:20:36 +01:00
Core/Mail: Replaced blocking db query in mail sending with async version
(cherry picked from commit 0a9e239c12)
This commit is contained in:
@@ -144,201 +144,190 @@ void WorldSession::HandleSendMail(WorldPackets::Mail::SendMail& sendMail)
|
||||
return;
|
||||
}
|
||||
|
||||
Player* receiver = ObjectAccessor::FindConnectedPlayer(receiverGuid);
|
||||
|
||||
uint32 receiverTeam = 0;
|
||||
uint8 mailsCount = 0; //do not allow to send to one player more than 100 mails
|
||||
uint8 receiverLevel = 0;
|
||||
uint32 receiverAccountId = 0;
|
||||
uint32 receiverBnetAccountId = 0;
|
||||
|
||||
if (receiver)
|
||||
auto mailCountCheckContinuation = [this, player = _player, receiverGuid, mailInfo = std::move(sendMail.Info), reqmoney, cost](uint32 receiverTeam, uint64 mailsCount, uint8 receiverLevel, uint32 receiverAccountId, uint32 receiverBnetAccountId) mutable
|
||||
{
|
||||
receiverTeam = receiver->GetTeam();
|
||||
mailsCount = receiver->GetMailSize();
|
||||
receiverLevel = receiver->GetLevel();
|
||||
receiverAccountId = receiver->GetSession()->GetAccountId();
|
||||
receiverBnetAccountId = receiver->GetSession()->GetBattlenetAccountId();
|
||||
if (_player != player)
|
||||
return;
|
||||
|
||||
// do not allow to have more than 100 mails in mailbox.. mails count is in opcode uint8!!! - so max can be 255..
|
||||
if (mailsCount > 100)
|
||||
{
|
||||
player->SendMailResult(0, MAIL_SEND, MAIL_ERR_RECIPIENT_CAP_REACHED);
|
||||
return;
|
||||
}
|
||||
|
||||
// test the receiver's Faction... or all items are account bound
|
||||
bool accountBound = !mailInfo.Attachments.empty();
|
||||
for (auto const& att : mailInfo.Attachments)
|
||||
{
|
||||
if (Item* item = player->GetItemByGuid(att.ItemGUID))
|
||||
{
|
||||
ItemTemplate const* itemProto = item->GetTemplate();
|
||||
if (!itemProto || !itemProto->HasFlag(ITEM_FLAG_IS_BOUND_TO_ACCOUNT))
|
||||
{
|
||||
accountBound = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!accountBound && player->GetTeam() != receiverTeam && !HasPermission(rbac::RBAC_PERM_TWO_SIDE_INTERACTION_MAIL))
|
||||
{
|
||||
player->SendMailResult(0, MAIL_SEND, MAIL_ERR_NOT_YOUR_TEAM);
|
||||
return;
|
||||
}
|
||||
|
||||
if (receiverLevel < sWorld->getIntConfig(CONFIG_MAIL_LEVEL_REQ))
|
||||
{
|
||||
SendNotification(GetTrinityString(LANG_MAIL_RECEIVER_REQ), sWorld->getIntConfig(CONFIG_MAIL_LEVEL_REQ));
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<Item*> items;
|
||||
|
||||
for (auto const& att : mailInfo.Attachments)
|
||||
{
|
||||
if (att.ItemGUID.IsEmpty())
|
||||
{
|
||||
player->SendMailResult(0, MAIL_SEND, MAIL_ERR_MAIL_ATTACHMENT_INVALID);
|
||||
return;
|
||||
}
|
||||
|
||||
Item* item = player->GetItemByGuid(att.ItemGUID);
|
||||
|
||||
// prevent sending bag with items (cheat: can be placed in bag after adding equipped empty bag to mail)
|
||||
if (!item)
|
||||
{
|
||||
player->SendMailResult(0, MAIL_SEND, MAIL_ERR_MAIL_ATTACHMENT_INVALID);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!item->CanBeTraded(true))
|
||||
{
|
||||
player->SendMailResult(0, MAIL_SEND, MAIL_ERR_EQUIP_ERROR, EQUIP_ERR_MAIL_BOUND_ITEM);
|
||||
return;
|
||||
}
|
||||
|
||||
if (item->IsBoundAccountWide() && item->IsSoulBound() && player->GetSession()->GetAccountId() != receiverAccountId)
|
||||
{
|
||||
if (!item->IsBattlenetAccountBound() || !player->GetSession()->GetBattlenetAccountId() || player->GetSession()->GetBattlenetAccountId() != receiverBnetAccountId)
|
||||
{
|
||||
player->SendMailResult(0, MAIL_SEND, MAIL_ERR_EQUIP_ERROR, EQUIP_ERR_NOT_SAME_ACCOUNT);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (item->GetTemplate()->HasFlag(ITEM_FLAG_CONJURED) || *item->m_itemData->Expiration)
|
||||
{
|
||||
player->SendMailResult(0, MAIL_SEND, MAIL_ERR_EQUIP_ERROR, EQUIP_ERR_MAIL_BOUND_ITEM);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mailInfo.Cod && item->IsWrapped())
|
||||
{
|
||||
player->SendMailResult(0, MAIL_SEND, MAIL_ERR_CANT_SEND_WRAPPED_COD);
|
||||
return;
|
||||
}
|
||||
|
||||
if (item->IsNotEmptyBag())
|
||||
{
|
||||
player->SendMailResult(0, MAIL_SEND, MAIL_ERR_EQUIP_ERROR, EQUIP_ERR_DESTROY_NONEMPTY_BAG);
|
||||
return;
|
||||
}
|
||||
|
||||
items.push_back(item);
|
||||
}
|
||||
|
||||
player->SendMailResult(0, MAIL_SEND, MAIL_OK);
|
||||
|
||||
player->ModifyMoney(-reqmoney);
|
||||
player->UpdateCriteria(CriteriaType::MoneySpentOnPostage, cost);
|
||||
|
||||
bool needItemDelay = false;
|
||||
|
||||
MailDraft draft(mailInfo.Subject, mailInfo.Body);
|
||||
|
||||
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
|
||||
|
||||
if (!mailInfo.Attachments.empty() || mailInfo.SendMoney > 0)
|
||||
{
|
||||
bool log = HasPermission(rbac::RBAC_PERM_LOG_GM_TRADE);
|
||||
if (!mailInfo.Attachments.empty())
|
||||
{
|
||||
for (auto const& item : items)
|
||||
{
|
||||
if (log)
|
||||
{
|
||||
sLog->outCommand(GetAccountId(), "GM %s (%s) (Account: %u) mail item: %s (Entry: %u Count: %u) "
|
||||
"to: %s (%s) (Account: %u)", GetPlayerName().c_str(), _player->GetGUID().ToString().c_str(), GetAccountId(),
|
||||
item->GetTemplate()->GetDefaultLocaleName(), item->GetEntry(), item->GetCount(),
|
||||
mailInfo.Target.c_str(), receiverGuid.ToString().c_str(), receiverAccountId);
|
||||
}
|
||||
|
||||
item->SetNotRefundable(GetPlayer()); // makes the item no longer refundable
|
||||
player->MoveItemFromInventory(item->GetBagSlot(), item->GetSlot(), true);
|
||||
|
||||
item->DeleteFromInventoryDB(trans); // deletes item from character's inventory
|
||||
item->SetOwnerGUID(receiverGuid);
|
||||
item->SetState(ITEM_CHANGED);
|
||||
item->SaveToDB(trans); // recursive and not have transaction guard into self, item not in inventory and can be save standalone
|
||||
|
||||
draft.AddItem(item);
|
||||
}
|
||||
|
||||
// if item send to character at another account, then apply item delivery delay
|
||||
needItemDelay = player->GetSession()->GetAccountId() != receiverAccountId;
|
||||
}
|
||||
|
||||
if (log && mailInfo.SendMoney > 0)
|
||||
{
|
||||
sLog->outCommand(GetAccountId(), "GM %s (%s) (Account: %u) mail money: " SI64FMTD " to: %s (%s) (Account: %u)",
|
||||
GetPlayerName().c_str(), _player->GetGUID().ToString().c_str(), GetAccountId(), mailInfo.SendMoney, mailInfo.Target.c_str(), receiverGuid.ToString().c_str(), receiverAccountId);
|
||||
}
|
||||
}
|
||||
|
||||
// If theres is an item, there is a one hour delivery delay if sent to another account's character.
|
||||
uint32 deliver_delay = needItemDelay ? sWorld->getIntConfig(CONFIG_MAIL_DELIVERY_DELAY) : 0;
|
||||
|
||||
// Mail sent between guild members arrives instantly
|
||||
if (Guild* guild = sGuildMgr->GetGuildById(player->GetGuildId()))
|
||||
if (guild->IsMember(receiverGuid))
|
||||
deliver_delay = 0;
|
||||
|
||||
// don't ask for COD if there are no items
|
||||
if (mailInfo.Attachments.empty())
|
||||
mailInfo.Cod = 0;
|
||||
|
||||
// will delete item or place to receiver mail list
|
||||
draft
|
||||
.AddMoney(mailInfo.SendMoney)
|
||||
.AddCOD(mailInfo.Cod)
|
||||
.SendMailTo(trans, MailReceiver(ObjectAccessor::FindConnectedPlayer(receiverGuid), receiverGuid.GetCounter()), MailSender(player), mailInfo.Body.empty() ? MAIL_CHECK_MASK_COPIED : MAIL_CHECK_MASK_HAS_BODY, deliver_delay);
|
||||
|
||||
player->SaveInventoryAndGoldToDB(trans);
|
||||
CharacterDatabase.CommitTransaction(trans);
|
||||
};
|
||||
|
||||
if (Player* receiver = ObjectAccessor::FindConnectedPlayer(receiverGuid))
|
||||
{
|
||||
mailCountCheckContinuation(receiver->GetTeam(), receiver->GetMailSize(), receiver->GetLevel(), receiver->GetSession()->GetAccountId(), receiver->GetSession()->GetBattlenetAccountId());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (CharacterCacheEntry const* characterInfo = sCharacterCache->GetCharacterCacheByGuid(receiverGuid))
|
||||
{
|
||||
receiverTeam = Player::TeamForRace(characterInfo->Race);
|
||||
receiverLevel = characterInfo->Level;
|
||||
receiverAccountId = characterInfo->AccountId;
|
||||
}
|
||||
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_MAIL_COUNT);
|
||||
stmt->setUInt64(0, receiverGuid.GetCounter());
|
||||
|
||||
PreparedQueryResult result = CharacterDatabase.Query(stmt);
|
||||
if (result)
|
||||
GetQueryProcessor().AddCallback(CharacterDatabase.AsyncQuery(stmt)
|
||||
.WithChainingPreparedCallback([continuation = std::move(mailCountCheckContinuation), receiverGuid](QueryCallback& queryCallback, PreparedQueryResult mailCountResult) mutable
|
||||
{
|
||||
Field* fields = result->Fetch();
|
||||
mailsCount = fields[0].GetUInt64();
|
||||
}
|
||||
|
||||
receiverBnetAccountId = Battlenet::AccountMgr::GetIdByGameAccount(receiverAccountId);
|
||||
}
|
||||
|
||||
// do not allow to have more than 100 mails in mailbox.. mails count is in opcode uint8!!! - so max can be 255..
|
||||
if (mailsCount > 100)
|
||||
{
|
||||
player->SendMailResult(0, MAIL_SEND, MAIL_ERR_RECIPIENT_CAP_REACHED);
|
||||
return;
|
||||
}
|
||||
|
||||
// test the receiver's Faction... or all items are account bound
|
||||
bool accountBound = !sendMail.Info.Attachments.empty();
|
||||
for (auto const& att : sendMail.Info.Attachments)
|
||||
{
|
||||
if (Item* item = player->GetItemByGuid(att.ItemGUID))
|
||||
{
|
||||
ItemTemplate const* itemProto = item->GetTemplate();
|
||||
if (!itemProto || !itemProto->HasFlag(ITEM_FLAG_IS_BOUND_TO_ACCOUNT))
|
||||
{
|
||||
accountBound = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!accountBound && player->GetTeam() != receiverTeam && !HasPermission(rbac::RBAC_PERM_TWO_SIDE_INTERACTION_MAIL))
|
||||
{
|
||||
player->SendMailResult(0, MAIL_SEND, MAIL_ERR_NOT_YOUR_TEAM);
|
||||
return;
|
||||
}
|
||||
|
||||
if (receiverLevel < sWorld->getIntConfig(CONFIG_MAIL_LEVEL_REQ))
|
||||
{
|
||||
SendNotification(GetTrinityString(LANG_MAIL_RECEIVER_REQ), sWorld->getIntConfig(CONFIG_MAIL_LEVEL_REQ));
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<Item*> items;
|
||||
|
||||
for (auto const& att : sendMail.Info.Attachments)
|
||||
{
|
||||
if (att.ItemGUID.IsEmpty())
|
||||
{
|
||||
player->SendMailResult(0, MAIL_SEND, MAIL_ERR_MAIL_ATTACHMENT_INVALID);
|
||||
return;
|
||||
}
|
||||
|
||||
Item* item = player->GetItemByGuid(att.ItemGUID);
|
||||
|
||||
// prevent sending bag with items (cheat: can be placed in bag after adding equipped empty bag to mail)
|
||||
if (!item)
|
||||
{
|
||||
player->SendMailResult(0, MAIL_SEND, MAIL_ERR_MAIL_ATTACHMENT_INVALID);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!item->CanBeTraded(true))
|
||||
{
|
||||
player->SendMailResult(0, MAIL_SEND, MAIL_ERR_EQUIP_ERROR, EQUIP_ERR_MAIL_BOUND_ITEM);
|
||||
return;
|
||||
}
|
||||
|
||||
if (item->IsBoundAccountWide() && item->IsSoulBound() && player->GetSession()->GetAccountId() != receiverAccountId)
|
||||
{
|
||||
if (!item->IsBattlenetAccountBound() || !player->GetSession()->GetBattlenetAccountId() || player->GetSession()->GetBattlenetAccountId() != receiverBnetAccountId)
|
||||
{
|
||||
player->SendMailResult(0, MAIL_SEND, MAIL_ERR_EQUIP_ERROR, EQUIP_ERR_NOT_SAME_ACCOUNT);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (item->GetTemplate()->HasFlag(ITEM_FLAG_CONJURED) || *item->m_itemData->Expiration)
|
||||
{
|
||||
player->SendMailResult(0, MAIL_SEND, MAIL_ERR_EQUIP_ERROR, EQUIP_ERR_MAIL_BOUND_ITEM);
|
||||
return;
|
||||
}
|
||||
|
||||
if (sendMail.Info.Cod && item->IsWrapped())
|
||||
{
|
||||
player->SendMailResult(0, MAIL_SEND, MAIL_ERR_CANT_SEND_WRAPPED_COD);
|
||||
return;
|
||||
}
|
||||
|
||||
if (item->IsNotEmptyBag())
|
||||
{
|
||||
player->SendMailResult(0, MAIL_SEND, MAIL_ERR_EQUIP_ERROR, EQUIP_ERR_DESTROY_NONEMPTY_BAG);
|
||||
return;
|
||||
}
|
||||
|
||||
items.push_back(item);
|
||||
}
|
||||
|
||||
player->SendMailResult(0, MAIL_SEND, MAIL_OK);
|
||||
|
||||
player->ModifyMoney(-reqmoney);
|
||||
player->UpdateCriteria(CriteriaType::MoneySpentOnPostage, cost);
|
||||
|
||||
bool needItemDelay = false;
|
||||
|
||||
MailDraft draft(sendMail.Info.Subject, sendMail.Info.Body);
|
||||
|
||||
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
|
||||
|
||||
if (!sendMail.Info.Attachments.empty() || sendMail.Info.SendMoney > 0)
|
||||
{
|
||||
bool log = HasPermission(rbac::RBAC_PERM_LOG_GM_TRADE);
|
||||
if (!sendMail.Info.Attachments.empty())
|
||||
{
|
||||
for (auto const& item : items)
|
||||
{
|
||||
if (log)
|
||||
if (CharacterCacheEntry const* characterInfo = sCharacterCache->GetCharacterCacheByGuid(receiverGuid))
|
||||
queryCallback.WithPreparedCallback([continuation = std::move(continuation), characterInfo, mailCountResult](PreparedQueryResult bnetAccountResult) mutable
|
||||
{
|
||||
sLog->outCommand(GetAccountId(), "GM %s (%s) (Account: %u) mail item: %s (Entry: %u Count: %u) "
|
||||
"to: %s (%s) (Account: %u)", GetPlayerName().c_str(), _player->GetGUID().ToString().c_str(), GetAccountId(),
|
||||
item->GetTemplate()->GetDefaultLocaleName(), item->GetEntry(), item->GetCount(),
|
||||
sendMail.Info.Target.c_str(), receiverGuid.ToString().c_str(), receiverAccountId);
|
||||
}
|
||||
|
||||
item->SetNotRefundable(GetPlayer()); // makes the item no longer refundable
|
||||
player->MoveItemFromInventory(item->GetBagSlot(), item->GetSlot(), true);
|
||||
|
||||
item->DeleteFromInventoryDB(trans); // deletes item from character's inventory
|
||||
item->SetOwnerGUID(receiverGuid);
|
||||
item->SetState(ITEM_CHANGED);
|
||||
item->SaveToDB(trans); // recursive and not have transaction guard into self, item not in inventory and can be save standalone
|
||||
|
||||
draft.AddItem(item);
|
||||
}
|
||||
|
||||
// if item send to character at another account, then apply item delivery delay
|
||||
needItemDelay = player->GetSession()->GetAccountId() != receiverAccountId;
|
||||
}
|
||||
|
||||
if (log && sendMail.Info.SendMoney > 0)
|
||||
{
|
||||
sLog->outCommand(GetAccountId(), "GM %s (%s) (Account: %u) mail money: " SI64FMTD " to: %s (%s) (Account: %u)",
|
||||
GetPlayerName().c_str(), _player->GetGUID().ToString().c_str(), GetAccountId(), sendMail.Info.SendMoney, sendMail.Info.Target.c_str(), receiverGuid.ToString().c_str(), receiverAccountId);
|
||||
}
|
||||
continuation(Player::TeamForRace(characterInfo->Race), mailCountResult ? (*mailCountResult)[0].GetUInt64() : UI64LIT(0),
|
||||
characterInfo->Level, characterInfo->AccountId, bnetAccountResult ? (*bnetAccountResult)[0].GetUInt32() : 0);
|
||||
}).SetNextQuery(Battlenet::AccountMgr::GetIdByGameAccountAsync(characterInfo->AccountId));
|
||||
}));
|
||||
}
|
||||
|
||||
// If theres is an item, there is a one hour delivery delay if sent to another account's character.
|
||||
uint32 deliver_delay = needItemDelay ? sWorld->getIntConfig(CONFIG_MAIL_DELIVERY_DELAY) : 0;
|
||||
|
||||
// Mail sent between guild members arrives instantly
|
||||
if (Guild* guild = sGuildMgr->GetGuildById(player->GetGuildId()))
|
||||
if (guild->IsMember(receiverGuid))
|
||||
deliver_delay = 0;
|
||||
|
||||
// don't ask for COD if there are no items
|
||||
if (sendMail.Info.Attachments.empty())
|
||||
sendMail.Info.Cod = 0;
|
||||
|
||||
// will delete item or place to receiver mail list
|
||||
draft
|
||||
.AddMoney(sendMail.Info.SendMoney)
|
||||
.AddCOD(sendMail.Info.Cod)
|
||||
.SendMailTo(trans, MailReceiver(receiver, receiverGuid.GetCounter()), MailSender(player), sendMail.Info.Body.empty() ? MAIL_CHECK_MASK_COPIED : MAIL_CHECK_MASK_HAS_BODY, deliver_delay);
|
||||
|
||||
player->SaveInventoryAndGoldToDB(trans);
|
||||
CharacterDatabase.CommitTransaction(trans);
|
||||
}
|
||||
|
||||
//called when mail is read
|
||||
|
||||
Reference in New Issue
Block a user