Fix packets not being sent to players on loading screen even if the system supports these packets to be queued and sent till the player is in world. There might be additional cases where this applies, please report back if you find any. Fixes #11339
837 lines
29 KiB
C++
837 lines
29 KiB
C++
/*
|
|
* Copyright (C) 2008-2014 TrinityCore <http://www.trinitycore.org/>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2 of the License, or (at your
|
|
* option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "DatabaseEnv.h"
|
|
#include "Mail.h"
|
|
#include "WorldPacket.h"
|
|
#include "WorldSession.h"
|
|
#include "Opcodes.h"
|
|
#include "Log.h"
|
|
#include "World.h"
|
|
#include "ObjectMgr.h"
|
|
#include "Player.h"
|
|
#include "Language.h"
|
|
#include "DBCStores.h"
|
|
#include "Item.h"
|
|
#include "AccountMgr.h"
|
|
|
|
bool WorldSession::CanOpenMailBox(ObjectGuid guid)
|
|
{
|
|
if (guid == _player->GetGUID())
|
|
{
|
|
if (!HasPermission(rbac::RBAC_PERM_COMMAND_MAILBOX))
|
|
{
|
|
TC_LOG_WARN("cheat", "%s attempt open mailbox in cheating way.", _player->GetName().c_str());
|
|
return false;
|
|
}
|
|
}
|
|
else if (guid.IsGameObject())
|
|
{
|
|
if (!_player->GetGameObjectIfCanInteractWith(guid, GAMEOBJECT_TYPE_MAILBOX))
|
|
return false;
|
|
}
|
|
else if (guid.IsAnyTypeCreature())
|
|
{
|
|
if (!_player->GetNPCIfCanInteractWith(guid, UNIT_NPC_FLAG_MAILBOX))
|
|
return false;
|
|
}
|
|
else
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void WorldSession::HandleSendMail(WorldPacket& recvData)
|
|
{
|
|
ObjectGuid mailbox, unk3;
|
|
std::string receiverName, subject, body;
|
|
uint32 stationery, package, money, COD;
|
|
uint8 unk4;
|
|
uint8 items_count;
|
|
recvData >> mailbox >> receiverName >> subject >> body
|
|
>> stationery // stationery?
|
|
>> package // 0x00000000
|
|
>> items_count; // attached items count
|
|
|
|
if (items_count > MAX_MAIL_ITEMS) // client limit
|
|
{
|
|
GetPlayer()->SendMailResult(0, MAIL_SEND, MAIL_ERR_TOO_MANY_ATTACHMENTS);
|
|
recvData.rfinish(); // set to end to avoid warnings spam
|
|
return;
|
|
}
|
|
|
|
ObjectGuid itemGUIDs[MAX_MAIL_ITEMS];
|
|
|
|
for (uint8 i = 0; i < items_count; ++i)
|
|
{
|
|
recvData.read_skip<uint8>(); // item slot in mail, not used
|
|
recvData >> itemGUIDs[i];
|
|
}
|
|
|
|
recvData >> money >> COD; // money and cod
|
|
recvData >> unk3; // const 0
|
|
recvData >> unk4; // const 0
|
|
|
|
// packet read complete, now do check
|
|
|
|
if (!CanOpenMailBox(mailbox))
|
|
return;
|
|
|
|
if (receiverName.empty())
|
|
return;
|
|
|
|
Player* player = _player;
|
|
|
|
if (player->getLevel() < sWorld->getIntConfig(CONFIG_MAIL_LEVEL_REQ))
|
|
{
|
|
SendNotification(GetTrinityString(LANG_MAIL_SENDER_REQ), sWorld->getIntConfig(CONFIG_MAIL_LEVEL_REQ));
|
|
return;
|
|
}
|
|
|
|
ObjectGuid receiverGuid;
|
|
if (normalizePlayerName(receiverName))
|
|
receiverGuid = sObjectMgr->GetPlayerGUIDByName(receiverName);
|
|
|
|
if (!receiverGuid)
|
|
{
|
|
TC_LOG_INFO("network", "Player %u is sending mail to %s (GUID: not existed!) with subject %s "
|
|
"and body %s includes %u items, %u copper and %u COD copper with unk1 = %u, unk2 = %u",
|
|
player->GetGUIDLow(), receiverName.c_str(), subject.c_str(), body.c_str(),
|
|
items_count, money, COD, stationery, package);
|
|
player->SendMailResult(0, MAIL_SEND, MAIL_ERR_RECIPIENT_NOT_FOUND);
|
|
return;
|
|
}
|
|
|
|
TC_LOG_INFO("network", "Player %u is sending mail to %s (%s) with subject %s and body %s "
|
|
"includes %u items, %u copper and %u COD copper with unk1 = %u, unk2 = %u",
|
|
player->GetGUIDLow(), receiverName.c_str(), receiverGuid.ToString().c_str(), subject.c_str(),
|
|
body.c_str(), items_count, money, COD, stationery, package);
|
|
|
|
if (player->GetGUID() == receiverGuid)
|
|
{
|
|
player->SendMailResult(0, MAIL_SEND, MAIL_ERR_CANNOT_SEND_TO_SELF);
|
|
return;
|
|
}
|
|
|
|
uint32 cost = items_count ? 30 * items_count : 30; // price hardcoded in client
|
|
|
|
uint32 reqmoney = cost + money;
|
|
|
|
// Check for overflow
|
|
if (reqmoney < money)
|
|
{
|
|
player->SendMailResult(0, MAIL_SEND, MAIL_ERR_NOT_ENOUGH_MONEY);
|
|
return;
|
|
}
|
|
|
|
if (!player->HasEnoughMoney(reqmoney) && !player->IsGameMaster())
|
|
{
|
|
player->SendMailResult(0, MAIL_SEND, MAIL_ERR_NOT_ENOUGH_MONEY);
|
|
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;
|
|
|
|
if (receiver)
|
|
{
|
|
receiverTeam = receiver->GetTeam();
|
|
mailsCount = receiver->GetMailSize();
|
|
receiverLevel = receiver->getLevel();
|
|
receiverAccountId = receiver->GetSession()->GetAccountId();
|
|
}
|
|
else
|
|
{
|
|
receiverTeam = sObjectMgr->GetPlayerTeamByGUID(receiverGuid);
|
|
|
|
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_MAIL_COUNT);
|
|
stmt->setUInt32(0, receiverGuid.GetCounter());
|
|
|
|
PreparedQueryResult result = CharacterDatabase.Query(stmt);
|
|
if (result)
|
|
{
|
|
Field* fields = result->Fetch();
|
|
mailsCount = fields[0].GetUInt64();
|
|
}
|
|
|
|
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHAR_LEVEL);
|
|
stmt->setUInt32(0, receiverGuid.GetCounter());
|
|
|
|
result = CharacterDatabase.Query(stmt);
|
|
if (result)
|
|
{
|
|
Field* fields = result->Fetch();
|
|
receiverLevel = fields[0].GetUInt8();
|
|
}
|
|
|
|
receiverAccountId = sObjectMgr->GetPlayerAccountIdByGUID(receiverGuid);
|
|
}
|
|
|
|
// 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 = items_count ? true : false;
|
|
for (uint8 i = 0; i < items_count; ++i)
|
|
{
|
|
if (Item* item = player->GetItemByGuid(itemGUIDs[i]))
|
|
{
|
|
ItemTemplate const* itemProto = item->GetTemplate();
|
|
if (!itemProto || !(itemProto->Flags & ITEM_PROTO_FLAG_BIND_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;
|
|
}
|
|
|
|
Item* items[MAX_MAIL_ITEMS];
|
|
|
|
for (uint8 i = 0; i < items_count; ++i)
|
|
{
|
|
if (!itemGUIDs[i])
|
|
{
|
|
player->SendMailResult(0, MAIL_SEND, MAIL_ERR_MAIL_ATTACHMENT_INVALID);
|
|
return;
|
|
}
|
|
|
|
Item* item = player->GetItemByGuid(itemGUIDs[i]);
|
|
|
|
// 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)
|
|
{
|
|
player->SendMailResult(0, MAIL_SEND, MAIL_ERR_EQUIP_ERROR, EQUIP_ERR_ARTEFACTS_ONLY_FOR_OWN_CHARACTERS);
|
|
return;
|
|
}
|
|
|
|
if (item->GetTemplate()->Flags & ITEM_PROTO_FLAG_CONJURED || item->GetUInt32Value(ITEM_FIELD_DURATION))
|
|
{
|
|
player->SendMailResult(0, MAIL_SEND, MAIL_ERR_EQUIP_ERROR, EQUIP_ERR_MAIL_BOUND_ITEM);
|
|
return;
|
|
}
|
|
|
|
if (COD && item->HasFlag(ITEM_FIELD_FLAGS, ITEM_FLAG_WRAPPED))
|
|
{
|
|
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_CAN_ONLY_DO_WITH_EMPTY_BAGS);
|
|
return;
|
|
}
|
|
|
|
items[i] = item;
|
|
}
|
|
|
|
player->SendMailResult(0, MAIL_SEND, MAIL_OK);
|
|
|
|
player->ModifyMoney(-int32(reqmoney));
|
|
player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_GOLD_SPENT_FOR_MAIL, cost);
|
|
|
|
bool needItemDelay = false;
|
|
|
|
MailDraft draft(subject, body);
|
|
|
|
SQLTransaction trans = CharacterDatabase.BeginTransaction();
|
|
|
|
if (items_count > 0 || money > 0)
|
|
{
|
|
bool log = HasPermission(rbac::RBAC_PERM_LOG_GM_TRADE);
|
|
if (items_count > 0)
|
|
{
|
|
for (uint8 i = 0; i < items_count; ++i)
|
|
{
|
|
Item* item = items[i];
|
|
if (log)
|
|
{
|
|
sLog->outCommand(GetAccountId(), "GM %s (GUID: %u) (Account: %u) mail item: %s (Entry: %u Count: %u) "
|
|
"to: %s (%s) (Account: %u)", GetPlayerName().c_str(), GetGuidLow(), GetAccountId(),
|
|
item->GetTemplate()->Name1.c_str(), item->GetEntry(), item->GetCount(),
|
|
receiverName.c_str(), receiverGuid.ToString().c_str(), receiverAccountId);
|
|
}
|
|
|
|
item->SetNotRefundable(GetPlayer()); // makes the item no longer refundable
|
|
player->MoveItemFromInventory(items[i]->GetBagSlot(), item->GetSlot(), true);
|
|
|
|
item->DeleteFromInventoryDB(trans); // deletes item from character's inventory
|
|
item->SetOwnerGUID(receiverGuid);
|
|
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 && money > 0)
|
|
{
|
|
sLog->outCommand(GetAccountId(), "GM %s (GUID: %u) (Account: %u) mail money: %u to: %s (%s) (Account: %u)",
|
|
GetPlayerName().c_str(), GetGuidLow(), GetAccountId(), money, receiverName.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;
|
|
|
|
// don't ask for COD if there are no items
|
|
if (items_count == 0)
|
|
COD = 0;
|
|
|
|
// will delete item or place to receiver mail list
|
|
draft
|
|
.AddMoney(money)
|
|
.AddCOD(COD)
|
|
.SendMailTo(trans, MailReceiver(receiver, receiverGuid.GetCounter()), MailSender(player), body.empty() ? MAIL_CHECK_MASK_COPIED : MAIL_CHECK_MASK_HAS_BODY, deliver_delay);
|
|
|
|
player->SaveInventoryAndGoldToDB(trans);
|
|
CharacterDatabase.CommitTransaction(trans);
|
|
}
|
|
|
|
//called when mail is read
|
|
void WorldSession::HandleMailMarkAsRead(WorldPacket& recvData)
|
|
{
|
|
ObjectGuid mailbox;
|
|
uint32 mailId;
|
|
recvData >> mailbox;
|
|
recvData >> mailId;
|
|
|
|
if (!CanOpenMailBox(mailbox))
|
|
return;
|
|
|
|
Player* player = _player;
|
|
Mail* m = player->GetMail(mailId);
|
|
if (m && m->state != MAIL_STATE_DELETED)
|
|
{
|
|
if (player->unReadMails)
|
|
--player->unReadMails;
|
|
m->checked = m->checked | MAIL_CHECK_MASK_READ;
|
|
player->m_mailsUpdated = true;
|
|
m->state = MAIL_STATE_CHANGED;
|
|
}
|
|
}
|
|
|
|
//called when client deletes mail
|
|
void WorldSession::HandleMailDelete(WorldPacket& recvData)
|
|
{
|
|
ObjectGuid mailbox;
|
|
uint32 mailId;
|
|
recvData >> mailbox;
|
|
recvData >> mailId;
|
|
recvData.read_skip<uint32>(); // mailTemplateId
|
|
|
|
if (!CanOpenMailBox(mailbox))
|
|
return;
|
|
|
|
Mail* m = _player->GetMail(mailId);
|
|
Player* player = _player;
|
|
player->m_mailsUpdated = true;
|
|
if (m)
|
|
{
|
|
// delete shouldn't show up for COD mails
|
|
if (m->COD)
|
|
{
|
|
player->SendMailResult(mailId, MAIL_DELETED, MAIL_ERR_INTERNAL_ERROR);
|
|
return;
|
|
}
|
|
|
|
m->state = MAIL_STATE_DELETED;
|
|
}
|
|
player->SendMailResult(mailId, MAIL_DELETED, MAIL_OK);
|
|
}
|
|
|
|
void WorldSession::HandleMailReturnToSender(WorldPacket& recvData)
|
|
{
|
|
ObjectGuid mailbox;
|
|
uint32 mailId;
|
|
recvData >> mailbox;
|
|
recvData >> mailId;
|
|
recvData.read_skip<uint64>(); // original sender GUID for return to, not used
|
|
|
|
if (!CanOpenMailBox(mailbox))
|
|
return;
|
|
|
|
Player* player = _player;
|
|
Mail* m = player->GetMail(mailId);
|
|
if (!m || m->state == MAIL_STATE_DELETED || m->deliver_time > time(NULL))
|
|
{
|
|
player->SendMailResult(mailId, MAIL_RETURNED_TO_SENDER, MAIL_ERR_INTERNAL_ERROR);
|
|
return;
|
|
}
|
|
//we can return mail now
|
|
//so firstly delete the old one
|
|
SQLTransaction trans = CharacterDatabase.BeginTransaction();
|
|
|
|
PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_BY_ID);
|
|
stmt->setUInt32(0, mailId);
|
|
trans->Append(stmt);
|
|
|
|
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_ITEM_BY_ID);
|
|
stmt->setUInt32(0, mailId);
|
|
trans->Append(stmt);
|
|
|
|
player->RemoveMail(mailId);
|
|
|
|
// only return mail if the player exists (and delete if not existing)
|
|
if (m->messageType == MAIL_NORMAL && m->sender)
|
|
{
|
|
MailDraft draft(m->subject, m->body);
|
|
if (m->mailTemplateId)
|
|
draft = MailDraft(m->mailTemplateId, false); // items already included
|
|
|
|
if (m->HasItems())
|
|
{
|
|
for (MailItemInfoVec::iterator itr2 = m->items.begin(); itr2 != m->items.end(); ++itr2)
|
|
{
|
|
Item* item = player->GetMItem(itr2->item_guid);
|
|
if (item)
|
|
draft.AddItem(item);
|
|
else
|
|
{
|
|
//WTF?
|
|
}
|
|
|
|
player->RemoveMItem(itr2->item_guid);
|
|
}
|
|
}
|
|
draft.AddMoney(m->money).SendReturnToSender(GetAccountId(), m->receiver, m->sender, trans);
|
|
}
|
|
|
|
CharacterDatabase.CommitTransaction(trans);
|
|
|
|
delete m; //we can deallocate old mail
|
|
player->SendMailResult(mailId, MAIL_RETURNED_TO_SENDER, MAIL_OK);
|
|
}
|
|
|
|
//called when player takes item attached in mail
|
|
void WorldSession::HandleMailTakeItem(WorldPacket& recvData)
|
|
{
|
|
ObjectGuid mailbox;
|
|
uint32 mailId;
|
|
uint32 itemId;
|
|
recvData >> mailbox;
|
|
recvData >> mailId;
|
|
recvData >> itemId; // item guid low
|
|
|
|
if (!CanOpenMailBox(mailbox))
|
|
return;
|
|
|
|
Player* player = _player;
|
|
|
|
Mail* m = player->GetMail(mailId);
|
|
if (!m || m->state == MAIL_STATE_DELETED || m->deliver_time > time(NULL))
|
|
{
|
|
player->SendMailResult(mailId, MAIL_ITEM_TAKEN, MAIL_ERR_INTERNAL_ERROR);
|
|
return;
|
|
}
|
|
|
|
// verify that the mail has the item to avoid cheaters taking COD items without paying
|
|
if (std::find_if(m->items.begin(), m->items.end(), [itemId](MailItemInfo info){ return info.item_guid == itemId; }) == m->items.end())
|
|
{
|
|
player->SendMailResult(mailId, MAIL_ITEM_TAKEN, MAIL_ERR_INTERNAL_ERROR);
|
|
return;
|
|
}
|
|
|
|
// prevent cheating with skip client money check
|
|
if (!player->HasEnoughMoney(m->COD))
|
|
{
|
|
player->SendMailResult(mailId, MAIL_ITEM_TAKEN, MAIL_ERR_NOT_ENOUGH_MONEY);
|
|
return;
|
|
}
|
|
|
|
Item* it = player->GetMItem(itemId);
|
|
|
|
ItemPosCountVec dest;
|
|
uint8 msg = _player->CanStoreItem(NULL_BAG, NULL_SLOT, dest, it, false);
|
|
if (msg == EQUIP_ERR_OK)
|
|
{
|
|
SQLTransaction trans = CharacterDatabase.BeginTransaction();
|
|
m->RemoveItem(itemId);
|
|
m->removedItems.push_back(itemId);
|
|
|
|
if (m->COD > 0) //if there is COD, take COD money from player and send them to sender by mail
|
|
{
|
|
ObjectGuid sender_guid(HIGHGUID_PLAYER, m->sender);
|
|
Player* receiver = ObjectAccessor::FindConnectedPlayer(sender_guid);
|
|
|
|
uint32 sender_accId = 0;
|
|
|
|
if (HasPermission(rbac::RBAC_PERM_LOG_GM_TRADE))
|
|
{
|
|
std::string sender_name;
|
|
if (receiver)
|
|
{
|
|
sender_accId = receiver->GetSession()->GetAccountId();
|
|
sender_name = receiver->GetName();
|
|
}
|
|
else
|
|
{
|
|
// can be calculated early
|
|
sender_accId = sObjectMgr->GetPlayerAccountIdByGUID(sender_guid);
|
|
|
|
if (!sObjectMgr->GetPlayerNameByGUID(sender_guid, sender_name))
|
|
sender_name = sObjectMgr->GetTrinityStringForDBCLocale(LANG_UNKNOWN);
|
|
}
|
|
sLog->outCommand(GetAccountId(), "GM %s (Account: %u) receiver mail item: %s (Entry: %u Count: %u) and send COD money: %u to player: %s (Account: %u)",
|
|
GetPlayerName().c_str(), GetAccountId(), it->GetTemplate()->Name1.c_str(), it->GetEntry(), it->GetCount(), m->COD, sender_name.c_str(), sender_accId);
|
|
}
|
|
else if (!receiver)
|
|
sender_accId = sObjectMgr->GetPlayerAccountIdByGUID(sender_guid);
|
|
|
|
// check player existence
|
|
if (receiver || sender_accId)
|
|
{
|
|
MailDraft(m->subject, "")
|
|
.AddMoney(m->COD)
|
|
.SendMailTo(trans, MailReceiver(receiver, m->sender), MailSender(MAIL_NORMAL, m->receiver), MAIL_CHECK_MASK_COD_PAYMENT);
|
|
}
|
|
|
|
player->ModifyMoney(-int32(m->COD));
|
|
}
|
|
m->COD = 0;
|
|
m->state = MAIL_STATE_CHANGED;
|
|
player->m_mailsUpdated = true;
|
|
player->RemoveMItem(it->GetGUIDLow());
|
|
|
|
uint32 count = it->GetCount(); // save counts before store and possible merge with deleting
|
|
it->SetState(ITEM_UNCHANGED); // need to set this state, otherwise item cannot be removed later, if neccessary
|
|
player->MoveItemToInventory(dest, it, true);
|
|
|
|
player->SaveInventoryAndGoldToDB(trans);
|
|
player->_SaveMail(trans);
|
|
CharacterDatabase.CommitTransaction(trans);
|
|
|
|
player->SendMailResult(mailId, MAIL_ITEM_TAKEN, MAIL_OK, 0, itemId, count);
|
|
}
|
|
else
|
|
player->SendMailResult(mailId, MAIL_ITEM_TAKEN, MAIL_ERR_EQUIP_ERROR, msg);
|
|
}
|
|
|
|
void WorldSession::HandleMailTakeMoney(WorldPacket& recvData)
|
|
{
|
|
ObjectGuid mailbox;
|
|
uint32 mailId;
|
|
recvData >> mailbox;
|
|
recvData >> mailId;
|
|
|
|
if (!CanOpenMailBox(mailbox))
|
|
return;
|
|
|
|
Player* player = _player;
|
|
|
|
Mail* m = player->GetMail(mailId);
|
|
if (!m || m->state == MAIL_STATE_DELETED || m->deliver_time > time(NULL))
|
|
{
|
|
player->SendMailResult(mailId, MAIL_MONEY_TAKEN, MAIL_ERR_INTERNAL_ERROR);
|
|
return;
|
|
}
|
|
|
|
if (!player->ModifyMoney(m->money, false))
|
|
{
|
|
player->SendMailResult(mailId, MAIL_MONEY_TAKEN, MAIL_ERR_EQUIP_ERROR, EQUIP_ERR_TOO_MUCH_GOLD);
|
|
return;
|
|
}
|
|
|
|
m->money = 0;
|
|
m->state = MAIL_STATE_CHANGED;
|
|
player->m_mailsUpdated = true;
|
|
|
|
player->SendMailResult(mailId, MAIL_MONEY_TAKEN, MAIL_OK);
|
|
|
|
// save money and mail to prevent cheating
|
|
SQLTransaction trans = CharacterDatabase.BeginTransaction();
|
|
player->SaveGoldToDB(trans);
|
|
player->_SaveMail(trans);
|
|
CharacterDatabase.CommitTransaction(trans);
|
|
}
|
|
|
|
//called when player lists his received mails
|
|
void WorldSession::HandleGetMailList(WorldPacket& recvData)
|
|
{
|
|
ObjectGuid mailbox;
|
|
recvData >> mailbox;
|
|
|
|
if (!CanOpenMailBox(mailbox))
|
|
return;
|
|
|
|
Player* player = _player;
|
|
|
|
//load players mails, and mailed items
|
|
if (!player->m_mailsLoaded)
|
|
player->_LoadMail();
|
|
|
|
// client can't work with packets > max int16 value
|
|
const uint32 maxPacketSize = 32767;
|
|
|
|
uint32 mailsCount = 0; // real send to client mails amount
|
|
uint32 realCount = 0; // real mails amount
|
|
|
|
WorldPacket data(SMSG_MAIL_LIST_RESULT, (200)); // guess size
|
|
data << uint32(0); // real mail's count
|
|
data << uint8(0); // mail's count
|
|
time_t cur_time = time(NULL);
|
|
|
|
for (PlayerMails::iterator itr = player->GetMailBegin(); itr != player->GetMailEnd(); ++itr)
|
|
{
|
|
// Only first 50 mails are displayed
|
|
if (mailsCount >= 50)
|
|
{
|
|
realCount += 1;
|
|
continue;
|
|
}
|
|
|
|
// skip deleted or not delivered (deliver delay not expired) mails
|
|
if ((*itr)->state == MAIL_STATE_DELETED || cur_time < (*itr)->deliver_time)
|
|
continue;
|
|
|
|
uint8 item_count = (*itr)->items.size(); // max count is MAX_MAIL_ITEMS (12)
|
|
|
|
size_t next_mail_size = 2+4+1+((*itr)->messageType == MAIL_NORMAL ? 8 : 4)+4*8+((*itr)->subject.size()+1)+((*itr)->body.size()+1)+1+item_count*(1+4+4+MAX_INSPECTED_ENCHANTMENT_SLOT*3*4+4+4+4+4+4+4+1);
|
|
|
|
if (data.wpos()+next_mail_size > maxPacketSize)
|
|
{
|
|
realCount += 1;
|
|
continue;
|
|
}
|
|
|
|
data << uint16(next_mail_size); // Message size
|
|
data << uint32((*itr)->messageID); // Message ID
|
|
data << uint8((*itr)->messageType); // Message Type
|
|
|
|
switch ((*itr)->messageType)
|
|
{
|
|
case MAIL_NORMAL: // sender guid
|
|
data << ObjectGuid(HIGHGUID_PLAYER, (*itr)->sender);
|
|
break;
|
|
case MAIL_CREATURE:
|
|
case MAIL_GAMEOBJECT:
|
|
case MAIL_AUCTION:
|
|
case MAIL_CALENDAR:
|
|
data << uint32((*itr)->sender); // creature/gameobject entry, auction id, calendar event id?
|
|
break;
|
|
}
|
|
|
|
data << uint32((*itr)->COD); // COD
|
|
data << uint32(0); // package (Package.dbc)
|
|
data << uint32((*itr)->stationery); // stationery (Stationery.dbc)
|
|
data << uint32((*itr)->money); // Gold
|
|
data << uint32((*itr)->checked); // flags
|
|
data << float(float((*itr)->expire_time-time(NULL))/DAY); // Time
|
|
data << uint32((*itr)->mailTemplateId); // mail template (MailTemplate.dbc)
|
|
data << (*itr)->subject; // Subject string - once 00, when mail type = 3, max 256
|
|
data << (*itr)->body; // message? max 8000
|
|
data << uint8(item_count); // client limit is 0x10
|
|
|
|
for (uint8 i = 0; i < item_count; ++i)
|
|
{
|
|
Item* item = player->GetMItem((*itr)->items[i].item_guid);
|
|
// item index (0-6?)
|
|
data << uint8(i);
|
|
// item guid low?
|
|
data << uint32((item ? item->GetGUIDLow() : 0));
|
|
// entry
|
|
data << uint32((item ? item->GetEntry() : 0));
|
|
for (uint8 j = 0; j < MAX_INSPECTED_ENCHANTMENT_SLOT; ++j)
|
|
{
|
|
data << uint32((item ? item->GetEnchantmentId((EnchantmentSlot)j) : 0));
|
|
data << uint32((item ? item->GetEnchantmentDuration((EnchantmentSlot)j) : 0));
|
|
data << uint32((item ? item->GetEnchantmentCharges((EnchantmentSlot)j) : 0));
|
|
}
|
|
// can be negative
|
|
data << int32((item ? item->GetItemRandomPropertyId() : 0));
|
|
// unk
|
|
data << uint32((item ? item->GetItemSuffixFactor() : 0));
|
|
// stack count
|
|
data << uint32((item ? item->GetCount() : 0));
|
|
// charges
|
|
data << uint32((item ? item->GetSpellCharges() : 0));
|
|
// durability
|
|
data << uint32((item ? item->GetUInt32Value(ITEM_FIELD_MAXDURABILITY) : 0));
|
|
// durability
|
|
data << uint32((item ? item->GetUInt32Value(ITEM_FIELD_DURABILITY) : 0));
|
|
// unknown wotlk
|
|
data << uint8(0);
|
|
}
|
|
|
|
++realCount;
|
|
++mailsCount;
|
|
}
|
|
|
|
data.put<uint32>(0, realCount); // this will display warning about undelivered mail to player if realCount > mailsCount
|
|
data.put<uint8>(4, mailsCount); // set real send mails to client
|
|
SendPacket(&data);
|
|
|
|
// recalculate m_nextMailDelivereTime and unReadMails
|
|
_player->UpdateNextMailTimeAndUnreads();
|
|
}
|
|
|
|
//used when player copies mail body to his inventory
|
|
void WorldSession::HandleMailCreateTextItem(WorldPacket& recvData)
|
|
{
|
|
ObjectGuid mailbox;
|
|
uint32 mailId;
|
|
|
|
recvData >> mailbox;
|
|
recvData >> mailId;
|
|
|
|
if (!CanOpenMailBox(mailbox))
|
|
return;
|
|
|
|
Player* player = _player;
|
|
|
|
Mail* m = player->GetMail(mailId);
|
|
if (!m || (m->body.empty() && !m->mailTemplateId) || m->state == MAIL_STATE_DELETED || m->deliver_time > time(NULL) || (m->checked & MAIL_CHECK_MASK_COPIED))
|
|
{
|
|
player->SendMailResult(mailId, MAIL_MADE_PERMANENT, MAIL_ERR_INTERNAL_ERROR);
|
|
return;
|
|
}
|
|
|
|
Item* bodyItem = new Item; // This is not bag and then can be used new Item.
|
|
if (!bodyItem->Create(sObjectMgr->GenerateLowGuid(HIGHGUID_ITEM), MAIL_BODY_ITEM_TEMPLATE, player))
|
|
{
|
|
delete bodyItem;
|
|
return;
|
|
}
|
|
|
|
// in mail template case we need create new item text
|
|
if (m->mailTemplateId)
|
|
{
|
|
MailTemplateEntry const* mailTemplateEntry = sMailTemplateStore.LookupEntry(m->mailTemplateId);
|
|
if (!mailTemplateEntry)
|
|
{
|
|
player->SendMailResult(mailId, MAIL_MADE_PERMANENT, MAIL_ERR_INTERNAL_ERROR);
|
|
return;
|
|
}
|
|
|
|
bodyItem->SetText(mailTemplateEntry->content[GetSessionDbcLocale()]);
|
|
}
|
|
else
|
|
bodyItem->SetText(m->body);
|
|
|
|
if (m->messageType == MAIL_NORMAL)
|
|
bodyItem->SetGuidValue(ITEM_FIELD_CREATOR, ObjectGuid(HIGHGUID_PLAYER, m->sender));
|
|
|
|
bodyItem->SetFlag(ITEM_FIELD_FLAGS, ITEM_FLAG_MAIL_TEXT_MASK);
|
|
|
|
TC_LOG_INFO("network", "HandleMailCreateTextItem mailid=%u", mailId);
|
|
|
|
ItemPosCountVec dest;
|
|
uint8 msg = _player->CanStoreItem(NULL_BAG, NULL_SLOT, dest, bodyItem, false);
|
|
if (msg == EQUIP_ERR_OK)
|
|
{
|
|
m->checked = m->checked | MAIL_CHECK_MASK_COPIED;
|
|
m->state = MAIL_STATE_CHANGED;
|
|
player->m_mailsUpdated = true;
|
|
|
|
player->StoreItem(dest, bodyItem, true);
|
|
player->SendMailResult(mailId, MAIL_MADE_PERMANENT, MAIL_OK);
|
|
}
|
|
else
|
|
{
|
|
player->SendMailResult(mailId, MAIL_MADE_PERMANENT, MAIL_ERR_EQUIP_ERROR, msg);
|
|
delete bodyItem;
|
|
}
|
|
}
|
|
|
|
/// @todo Fix me! ... this void has probably bad condition, but good data are sent
|
|
void WorldSession::HandleQueryNextMailTime(WorldPacket & /*recvData*/)
|
|
{
|
|
WorldPacket data(MSG_QUERY_NEXT_MAIL_TIME, 8);
|
|
|
|
if (!_player->m_mailsLoaded)
|
|
_player->_LoadMail();
|
|
|
|
if (_player->unReadMails > 0)
|
|
{
|
|
data << float(0); // float
|
|
data << uint32(0); // count
|
|
|
|
uint32 count = 0;
|
|
time_t now = time(NULL);
|
|
std::set<uint32> sentSenders;
|
|
for (PlayerMails::iterator itr = _player->GetMailBegin(); itr != _player->GetMailEnd(); ++itr)
|
|
{
|
|
Mail* m = (*itr);
|
|
// must be not checked yet
|
|
if (m->checked & MAIL_CHECK_MASK_READ)
|
|
continue;
|
|
|
|
// and already delivered
|
|
if (now < m->deliver_time)
|
|
continue;
|
|
|
|
// only send each mail sender once
|
|
if (sentSenders.count(m->sender))
|
|
continue;
|
|
|
|
data << uint64(m->messageType == MAIL_NORMAL ? ObjectGuid(HIGHGUID_PLAYER, m->sender) : ObjectGuid::Empty); // player guid
|
|
data << uint32(m->messageType != MAIL_NORMAL ? m->sender : 0); // non-player entries
|
|
data << uint32(m->messageType);
|
|
data << uint32(m->stationery);
|
|
data << float(m->deliver_time - now);
|
|
|
|
sentSenders.insert(m->sender);
|
|
++count;
|
|
if (count == 2) // do not display more than 2 mails
|
|
break;
|
|
}
|
|
|
|
data.put<uint32>(4, count);
|
|
}
|
|
else
|
|
{
|
|
data << float(-DAY);
|
|
data << uint32(0);
|
|
}
|
|
|
|
SendPacket(&data);
|
|
}
|