diff options
Diffstat (limited to 'src/game/Mail.cpp')
-rw-r--r-- | src/game/Mail.cpp | 670 |
1 files changed, 381 insertions, 289 deletions
diff --git a/src/game/Mail.cpp b/src/game/Mail.cpp index 16f337136ea..ec775aa22a0 100644 --- a/src/game/Mail.cpp +++ b/src/game/Mail.cpp @@ -31,6 +31,9 @@ #include "Language.h" #include "AuctionHouseBot.h" #include "DBCStores.h" +#include "BattleGroundMgr.h" +#include "Item.h" +#include "AuctionHouseMgr.h" enum MailShowFlags { @@ -41,18 +44,6 @@ enum MailShowFlags MAIL_SHOW_RETURN = 0x0010, }; -void MailItem::deleteItem( bool inDB ) -{ - if(item) - { - if(inDB) - CharacterDatabase.PExecute("DELETE FROM item_instance WHERE guid='%u'", item->GetGUIDLow()); - - delete item; - item=NULL; - } -} - void WorldSession::HandleSendMail(WorldPacket & recv_data ) { uint64 mailbox, unk3; @@ -62,9 +53,6 @@ void WorldSession::HandleSendMail(WorldPacket & recv_data ) recv_data >> mailbox; recv_data >> receiver; - if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox, GAMEOBJECT_TYPE_MAILBOX)) - return; - recv_data >> subject; recv_data >> body; @@ -72,34 +60,32 @@ void WorldSession::HandleSendMail(WorldPacket & recv_data ) recv_data >> unk1; // stationery? recv_data >> unk2; // 0x00000000 - MailItemsInfo mi; - uint8 items_count; recv_data >> items_count; // attached items count - if(items_count > 12) // client limit + if (items_count > MAX_MAIL_ITEMS) // client limit { GetPlayer()->SendMailResult(0, MAIL_SEND, MAIL_ERR_TOO_MANY_ATTACHMENTS); + recv_data.rpos(recv_data.wpos()); // set to end to avoid warnings spam return; } - if(items_count) + uint64 itemGUIDs[MAX_MAIL_ITEMS]; + + for (uint8 i = 0; i < items_count; ++i) { - for (uint8 i = 0; i < items_count; ++i) - { - uint8 item_slot; - uint64 item_guid; - recv_data >> item_slot; - recv_data >> item_guid; - mi.AddItem(GUID_LOPART(item_guid), item_slot); - } + recv_data.read_skip<uint8>(); // item slot in mail, not used + recv_data >> itemGUIDs[i]; } recv_data >> money >> COD; // money and cod recv_data >> unk3; // const 0 recv_data >> unk4; // const 0 - items_count = mi.size(); // this is the real size after the duplicates have been removed + // packet read complete, now do check + + if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox, GAMEOBJECT_TYPE_MAILBOX)) + return; if (receiver.empty()) return; @@ -107,7 +93,7 @@ void WorldSession::HandleSendMail(WorldPacket & recv_data ) Player* pl = _player; uint64 rc = 0; - if(normalizePlayerName(receiver)) + if (normalizePlayerName(receiver)) rc = objmgr.GetPlayerGUIDByName(receiver); if (!rc) @@ -120,7 +106,7 @@ void WorldSession::HandleSendMail(WorldPacket & recv_data ) sLog.outDetail("Player %u is sending mail to %s (GUID: %u) with subject %s and body %s includes %u items, %u copper and %u COD copper with unk1 = %u, unk2 = %u", pl->GetGUIDLow(), receiver.c_str(), GUID_LOPART(rc), subject.c_str(), body.c_str(), items_count, money, COD, unk1, unk2); - if(pl->GetGUID() == rc) + if (pl->GetGUID() == rc) { pl->SendMailResult(0, MAIL_SEND, MAIL_ERR_CANNOT_SEND_TO_SELF); return; @@ -141,7 +127,7 @@ void WorldSession::HandleSendMail(WorldPacket & recv_data ) uint32 rc_team = 0; uint8 mails_count = 0; //do not allow to send to one player more than 100 mails - if(receive) + if (receive) { rc_team = receive->GetTeam(); mails_count = receive->GetMailSize(); @@ -149,8 +135,7 @@ void WorldSession::HandleSendMail(WorldPacket & recv_data ) else { rc_team = objmgr.GetPlayerTeamByGUID(rc); - QueryResult* result = CharacterDatabase.PQuery("SELECT COUNT(*) FROM mail WHERE receiver = '%u'", GUID_LOPART(rc)); - if(result) + if (QueryResult* result = CharacterDatabase.PQuery("SELECT COUNT(*) FROM mail WHERE receiver = '%u'", GUID_LOPART(rc))) { Field *fields = result->Fetch(); mails_count = fields[0].GetUInt32(); @@ -170,102 +155,96 @@ void WorldSession::HandleSendMail(WorldPacket & recv_data ) return; } - uint32 rc_account = 0; - if(receive) - rc_account = receive->GetSession()->GetAccountId(); - else - rc_account = objmgr.GetPlayerAccountIdByGUID(rc); + uint32 rc_account = receive + ? receive->GetSession()->GetAccountId() + : objmgr.GetPlayerAccountIdByGUID(rc); + + Item* items[MAX_MAIL_ITEMS]; - if (items_count) + for (uint8 i = 0; i < items_count; ++i) { - for (MailItemMap::iterator mailItemIter = mi.begin(); mailItemIter != mi.end(); ++mailItemIter) + if (!itemGUIDs[i]) { - MailItem& mailItem = mailItemIter->second; + pl->SendMailResult(0, MAIL_SEND, MAIL_ERR_MAIL_ATTACHMENT_INVALID); + return; + } - if(!mailItem.item_guidlow) - { - pl->SendMailResult(0, MAIL_SEND, MAIL_ERR_MAIL_ATTACHMENT_INVALID); - return; - } + Item* item = pl->GetItemByGuid(itemGUIDs[i]); - mailItem.item = pl->GetItemByGuid(MAKE_NEW_GUID(mailItem.item_guidlow, 0, HIGHGUID_ITEM)); - // prevent sending bag with items (cheat: can be placed in bag after adding equipped empty bag to mail) - if(!mailItem.item) - { - pl->SendMailResult(0, MAIL_SEND, MAIL_ERR_MAIL_ATTACHMENT_INVALID); - return; - } + // prevent sending bag with items (cheat: can be placed in bag after adding equipped empty bag to mail) + if (!item) + { + pl->SendMailResult(0, MAIL_SEND, MAIL_ERR_MAIL_ATTACHMENT_INVALID); + return; + } - if(!mailItem.item->CanBeTraded(true)) - { - pl->SendMailResult(0, MAIL_SEND, MAIL_ERR_EQUIP_ERROR, EQUIP_ERR_MAIL_BOUND_ITEM); - return; - } + if (!item->CanBeTraded(true)) + { + pl->SendMailResult(0, MAIL_SEND, MAIL_ERR_EQUIP_ERROR, EQUIP_ERR_MAIL_BOUND_ITEM); + return; + } - if(mailItem.item->IsBoundAccountWide() && mailItem.item->IsSoulBound() && pl->GetSession()->GetAccountId() != rc_account) - { - pl->SendMailResult(0, MAIL_SEND, MAIL_ERR_EQUIP_ERROR, EQUIP_ERR_ARTEFACTS_ONLY_FOR_OWN_CHARACTERS); - return; - } + if (item->IsBoundAccountWide() && item->IsSoulBound() && pl->GetSession()->GetAccountId() != rc_account) + { + pl->SendMailResult(0, MAIL_SEND, MAIL_ERR_EQUIP_ERROR, EQUIP_ERR_ARTEFACTS_ONLY_FOR_OWN_CHARACTERS); + return; + } - if (mailItem.item->HasFlag(ITEM_FIELD_FLAGS, ITEM_FLAGS_CONJURED) || mailItem.item->GetUInt32Value(ITEM_FIELD_DURATION)) - { - pl->SendMailResult(0, MAIL_SEND, MAIL_ERR_EQUIP_ERROR, EQUIP_ERR_MAIL_BOUND_ITEM); - return; - } + if (item->HasFlag(ITEM_FIELD_FLAGS, ITEM_FLAGS_CONJURED) || item->GetUInt32Value(ITEM_FIELD_DURATION)) + { + pl->SendMailResult(0, MAIL_SEND, MAIL_ERR_EQUIP_ERROR, EQUIP_ERR_MAIL_BOUND_ITEM); + return; + } - if(COD && mailItem.item->HasFlag(ITEM_FIELD_FLAGS, ITEM_FLAGS_WRAPPED)) - { - pl->SendMailResult(0, MAIL_SEND, MAIL_ERR_CANT_SEND_WRAPPED_COD); - return; - } + if (COD && item->HasFlag(ITEM_FIELD_FLAGS, ITEM_FLAGS_WRAPPED)) + { + pl->SendMailResult(0, MAIL_SEND, MAIL_ERR_CANT_SEND_WRAPPED_COD); + return; } + + items[i] = item; } + pl->SendMailResult(0, MAIL_SEND, MAIL_OK); - uint32 itemTextId = 0; - if (!body.empty()) - { - itemTextId = objmgr.CreateItemText( body ); - } + uint32 itemTextId = !body.empty() ? objmgr.CreateItemText( body ) : 0; pl->ModifyMoney( -int32(reqmoney) ); pl->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_GOLD_SPENT_FOR_MAIL, cost); bool needItemDelay = false; - if(items_count > 0 || money > 0) + MailDraft draft(subject, itemTextId); + + if (items_count > 0 || money > 0) { if (items_count > 0) { - for (MailItemMap::iterator mailItemIter = mi.begin(); mailItemIter != mi.end(); ++mailItemIter) + for (uint8 i = 0; i < items_count; ++i) { - MailItem& mailItem = mailItemIter->second; - if(!mailItem.item) - continue; - - mailItem.item_template = mailItem.item ? mailItem.item->GetEntry() : 0; - - if( GetSecurity() > SEC_PLAYER && sWorld.getConfig(CONFIG_GM_LOG_TRADE) ) + Item* item = items[i]; + if (GetSecurity() > SEC_PLAYER && sWorld.getConfig(CONFIG_GM_LOG_TRADE)) { sLog.outCommand(GetAccountId(), "GM %s (Account: %u) mail item: %s (Entry: %u Count: %u) to player: %s (Account: %u)", - GetPlayerName(), GetAccountId(), mailItem.item->GetProto()->Name1, mailItem.item->GetEntry(), mailItem.item->GetCount(), receiver.c_str(), rc_account); + GetPlayerName(), GetAccountId(), item->GetProto()->Name1, item->GetEntry(), item->GetCount(), receiver.c_str(), rc_account); } - pl->MoveItemFromInventory(mailItem.item->GetBagSlot(), mailItem.item->GetSlot(), true); + pl->MoveItemFromInventory(items[i]->GetBagSlot(), item->GetSlot(), true); CharacterDatabase.BeginTransaction(); - mailItem.item->DeleteFromInventoryDB(); //deletes item from character's inventory - mailItem.item->SaveToDB(); // recursive and not have transaction guard into self, item not in inventory and can be save standalone + item->DeleteFromInventoryDB(); // deletes item from character's inventory + item->SaveToDB(); // recursive and not have transaction guard into self, item not in inventory and can be save standalone // owner in data will set at mail receive and item extracting - CharacterDatabase.PExecute("UPDATE item_instance SET owner_guid = '%u' WHERE guid='%u'", GUID_LOPART(rc), mailItem.item->GetGUIDLow()); + CharacterDatabase.PExecute("UPDATE item_instance SET owner_guid = '%u' WHERE guid='%u'", GUID_LOPART(rc), item->GetGUIDLow()); CharacterDatabase.CommitTransaction(); + + draft.AddItem(item); } // if item send to character at another account, then apply item delivery delay needItemDelay = pl->GetSession()->GetAccountId() != rc_account; } - if(money > 0 && GetSecurity() > SEC_PLAYER && sWorld.getConfig(CONFIG_GM_LOG_TRADE)) + if (money > 0 && GetSecurity() > SEC_PLAYER && sWorld.getConfig(CONFIG_GM_LOG_TRADE)) { sLog.outCommand(GetAccountId(),"GM %s (Account: %u) mail money: %u to player: %s (Account: %u)", GetPlayerName(), GetAccountId(), money, receiver.c_str(), rc_account); @@ -276,7 +255,10 @@ void WorldSession::HandleSendMail(WorldPacket & recv_data ) uint32 deliver_delay = needItemDelay ? sWorld.getConfig(CONFIG_MAIL_DELIVERY_DELAY) : 0; // will delete item or place to receiver mail list - WorldSession::SendMailTo(receive, MAIL_NORMAL, MAIL_STATIONERY_NORMAL, pl->GetGUIDLow(), GUID_LOPART(rc), subject, itemTextId, &mi, money, COD, MAIL_CHECK_MASK_NONE, deliver_delay); + draft + .AddMoney(money) + .AddCOD(COD) + .SendMailTo(MailReceiver(receive, GUID_LOPART(rc)), pl, MAIL_CHECK_MASK_NONE, deliver_delay); CharacterDatabase.BeginTransaction(); pl->SaveInventoryAndGoldToDB(); @@ -289,11 +271,11 @@ void WorldSession::HandleMailMarkAsRead(WorldPacket & recv_data ) uint64 mailbox; uint32 mailId; recv_data >> mailbox; + recv_data >> mailId; if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox, GAMEOBJECT_TYPE_MAILBOX)) return; - recv_data >> mailId; Player *pl = _player; Mail *m = pl->GetMail(mailId); if (m) @@ -314,6 +296,7 @@ void WorldSession::HandleMailDelete(WorldPacket & recv_data ) uint32 mailId; recv_data >> mailbox; recv_data >> mailId; + recv_data.read_skip<uint32>(); // mailTemplateId if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox, GAMEOBJECT_TYPE_MAILBOX)) return; @@ -321,7 +304,7 @@ void WorldSession::HandleMailDelete(WorldPacket & recv_data ) Mail *m = _player->GetMail(mailId); Player* pl = _player; pl->m_mailsUpdated = true; - if(m) + if (m) { // delete shouldn't show up for COD mails if (m->COD) @@ -340,14 +323,15 @@ void WorldSession::HandleMailReturnToSender(WorldPacket & recv_data ) uint64 mailbox; uint32 mailId; recv_data >> mailbox; + recv_data >> mailId; + recv_data.read_skip<uint64>(); // original sender GUID for return to, not used if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox, GAMEOBJECT_TYPE_MAILBOX)) return; - recv_data >> mailId; Player *pl = _player; Mail *m = pl->GetMail(mailId); - if(!m || m->state == MAIL_STATE_DELETED || m->deliver_time > time(NULL)) + if (!m || m->state == MAIL_STATE_DELETED || m->deliver_time > time(NULL)) { pl->SendMailResult(mailId, MAIL_RETURNED_TO_SENDER, MAIL_ERR_INTERNAL_ERROR); return; @@ -361,84 +345,38 @@ void WorldSession::HandleMailReturnToSender(WorldPacket & recv_data ) CharacterDatabase.CommitTransaction(); pl->RemoveMail(mailId); - MailItemsInfo mi; - - if(m->HasItems()) + // send back only to players and simple drop for other cases + if (m->messageType == MAIL_NORMAL) { - for (std::vector<MailItemInfo>::iterator itr2 = m->items.begin(); itr2 != m->items.end(); ++itr2) + MailDraft draft(m->subject, m->itemTextId); + if (m->mailTemplateId) + draft = MailDraft(m->mailTemplateId, false); // items already included + + if (m->HasItems()) { - Item *item = pl->GetMItem(itr2->item_guid); - if(item) - mi.AddItem(item->GetGUIDLow(), item->GetEntry(), item); - else + for (std::vector<MailItemInfo>::iterator itr2 = m->items.begin(); itr2 != m->items.end(); ++itr2) { - //WTF? - } + Item *item = pl->GetMItem(itr2->item_guid); + if (item) + draft.AddItem(item); + else + { + //WTF? + } - pl->RemoveMItem(itr2->item_guid); + pl->RemoveMItem(itr2->item_guid); + } } - } - - if (m->sender == auctionbot.GetAHBplayerGUID()) - { - SendReturnToSender(MAIL_CREATURE, GetAccountId(), m->receiver, m->sender, m->subject, m->itemTextId, &mi, m->money, m->mailTemplateId); - } - else - { - SendReturnToSender(MAIL_NORMAL, GetAccountId(), m->receiver, m->sender, m->subject, m->itemTextId, &mi, m->money, m->mailTemplateId); + if (m->sender == auctionbot.GetAHBplayerGUID()) + draft.AddMoney(m->money).SendReturnToSender(GetAccountId(), m->receiver, auctionbot.GetAHBplayerGUID()); + else + draft.AddMoney(m->money).SendReturnToSender(GetAccountId(), m->receiver, m->sender); } delete m; //we can deallocate old mail pl->SendMailResult(mailId, MAIL_RETURNED_TO_SENDER, MAIL_OK); } -void WorldSession::SendReturnToSender(uint8 messageType, uint32 sender_acc, uint32 sender_guid, uint32 receiver_guid, const std::string& subject, uint32 itemTextId, MailItemsInfo *mi, uint32 money, uint16 mailTemplateId ) -{ - if(messageType != MAIL_NORMAL) // return only to players - { - mi->deleteIncludedItems(true); - return; - } - - Player *receiver = objmgr.GetPlayer(MAKE_NEW_GUID(receiver_guid, 0, HIGHGUID_PLAYER)); - - uint32 rc_account = 0; - if(!receiver) - rc_account = objmgr.GetPlayerAccountIdByGUID(MAKE_NEW_GUID(receiver_guid, 0, HIGHGUID_PLAYER)); - - if(!receiver && !rc_account) // sender not exist - { - mi->deleteIncludedItems(true); - return; - } - - // prepare mail and send in other case - bool needItemDelay = false; - - if(mi && !mi->empty()) - { - // if item send to character at another account, then apply item delivery delay - needItemDelay = sender_acc != rc_account; - - // set owner to new receiver (to prevent delete item with sender char deleting) - CharacterDatabase.BeginTransaction(); - for (MailItemMap::iterator mailItemIter = mi->begin(); mailItemIter != mi->end(); ++mailItemIter) - { - MailItem& mailItem = mailItemIter->second; - mailItem.item->SaveToDB(); // item not in inventory and can be save standalone - // owner in data will set at mail receive and item extracting - CharacterDatabase.PExecute("UPDATE item_instance SET owner_guid = '%u' WHERE guid='%u'", receiver_guid, mailItem.item->GetGUIDLow()); - } - CharacterDatabase.CommitTransaction(); - } - - // If theres is an item, there is a one hour delivery delay. - uint32 deliver_delay = needItemDelay ? sWorld.getConfig(CONFIG_MAIL_DELIVERY_DELAY) : 0; - - // will delete item or place to receiver mail list - WorldSession::SendMailTo(receiver, MAIL_NORMAL, MAIL_STATIONERY_NORMAL, sender_guid, receiver_guid, subject, itemTextId, mi, money, 0, MAIL_CHECK_MASK_RETURNED,deliver_delay,mailTemplateId); -} - //called when player takes item attached in mail void WorldSession::HandleMailTakeItem(WorldPacket & recv_data ) { @@ -446,23 +384,23 @@ void WorldSession::HandleMailTakeItem(WorldPacket & recv_data ) uint32 mailId; uint32 itemId; recv_data >> mailbox; + recv_data >> mailId; + recv_data >> itemId; // item guid low if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox, GAMEOBJECT_TYPE_MAILBOX)) return; - recv_data >> mailId; - recv_data >> itemId; // item guid low? Player* pl = _player; Mail* m = pl->GetMail(mailId); - if(!m || m->state == MAIL_STATE_DELETED || m->deliver_time > time(NULL)) + if (!m || m->state == MAIL_STATE_DELETED || m->deliver_time > time(NULL)) { pl->SendMailResult(mailId, MAIL_ITEM_TAKEN, MAIL_ERR_INTERNAL_ERROR); return; } // prevent cheating with skip client money check - if(pl->GetMoney() < m->COD) + if (pl->GetMoney() < m->COD) { pl->SendMailResult(mailId, MAIL_ITEM_TAKEN, MAIL_ERR_NOT_ENOUGH_MONEY); return; @@ -472,7 +410,7 @@ void WorldSession::HandleMailTakeItem(WorldPacket & recv_data ) ItemPosCountVec dest; uint8 msg = _player->CanStoreItem( NULL_BAG, NULL_SLOT, dest, it, false ); - if( msg == EQUIP_ERR_OK ) + if (msg == EQUIP_ERR_OK) { m->RemoveItem(itemId); m->removedItems.push_back(itemId); @@ -484,10 +422,10 @@ void WorldSession::HandleMailTakeItem(WorldPacket & recv_data ) uint32 sender_accId = 0; - if( GetSecurity() > SEC_PLAYER && sWorld.getConfig(CONFIG_GM_LOG_TRADE) ) + if (GetSecurity() > SEC_PLAYER && sWorld.getConfig(CONFIG_GM_LOG_TRADE)) { std::string sender_name; - if(receive) + if (receive) { sender_accId = receive->GetSession()->GetAccountId(); sender_name = receive->GetName(); @@ -497,19 +435,21 @@ void WorldSession::HandleMailTakeItem(WorldPacket & recv_data ) // can be calculated early sender_accId = objmgr.GetPlayerAccountIdByGUID(sender_guid); - if(!objmgr.GetPlayerNameByGUID(sender_guid,sender_name)) + if (!objmgr.GetPlayerNameByGUID(sender_guid,sender_name)) sender_name = objmgr.GetTrinityStringForDBCLocale(LANG_UNKNOWN); } sLog.outCommand(GetAccountId(),"GM %s (Account: %u) receive mail item: %s (Entry: %u Count: %u) and send COD money: %u to player: %s (Account: %u)", GetPlayerName(),GetAccountId(),it->GetProto()->Name1,it->GetEntry(),it->GetCount(),m->COD,sender_name.c_str(),sender_accId); } - else if(!receive) + else if (!receive) sender_accId = objmgr.GetPlayerAccountIdByGUID(sender_guid); // check player existence - if(receive || sender_accId) + if (receive || sender_accId) { - WorldSession::SendMailTo(receive, MAIL_NORMAL, MAIL_STATIONERY_NORMAL, m->receiver, m->sender, m->subject, 0, NULL, m->COD, 0, MAIL_CHECK_MASK_COD_PAYMENT); + MailDraft(m->subject) + .AddMoney(m->COD) + .SendMailTo(MailReceiver(receive,m->sender),MailSender(MAIL_NORMAL,m->receiver), MAIL_CHECK_MASK_COD_PAYMENT); } pl->ModifyMoney( -int32(m->COD) ); @@ -546,7 +486,7 @@ void WorldSession::HandleMailTakeMoney(WorldPacket & recv_data ) Player *pl = _player; Mail* m = pl->GetMail(mailId); - if(!m || m->state == MAIL_STATE_DELETED || m->deliver_time > time(NULL)) + if (!m || m->state == MAIL_STATE_DELETED || m->deliver_time > time(NULL)) { pl->SendMailResult(mailId, MAIL_MONEY_TAKEN, MAIL_ERR_INTERNAL_ERROR); return; @@ -578,20 +518,24 @@ void WorldSession::HandleGetMailList(WorldPacket & recv_data ) Player* pl = _player; //load players mails, and mailed items - if(!pl->m_mailsLoaded) + if (!pl->m_mailsLoaded) pl ->_LoadMail(); // client can't work with packets > max int16 value const uint32 maxPacketSize = 32767; - uint32 mails_count = 0; // real send to client mails amount + uint32 mailsCount = 0; // real send to client mails amount WorldPacket data(SMSG_MAIL_LIST_RESULT, (200)); // guess size data << uint8(0); // mail's count time_t cur_time = time(NULL); - for (PlayerMails::iterator itr = pl->GetmailBegin(); itr != pl->GetmailEnd(); ++itr) + for (PlayerMails::iterator itr = pl->GetMailBegin(); itr != pl->GetMailEnd(); ++itr) { + // packet send mail count as uint8, prevent overflow + if (mailsCount >= 254) + break; + // skip deleted or not delivered (deliver delay not expired) mails if ((*itr)->state == MAIL_STATE_DELETED || cur_time < (*itr)->deliver_time) continue; @@ -600,7 +544,7 @@ void WorldSession::HandleGetMailList(WorldPacket & recv_data ) size_t next_mail_size = 2+4+1+8+4*8+((*itr)->subject.size()+1)+1+item_count*(1+4+4+6*3*4+4+4+1+4+4+4); - if(data.wpos()+next_mail_size > maxPacketSize) + if (data.wpos()+next_mail_size > maxPacketSize) break; uint32 show_flags = 0; @@ -611,9 +555,9 @@ void WorldSession::HandleGetMailList(WorldPacket & recv_data ) if ((*itr)->HasItems() && (*itr)->messageType == MAIL_NORMAL) show_flags |= MAIL_SHOW_RETURN; - data << (uint16) 0x0040; // unknown 2.3.0, different values - data << (uint32) (*itr)->messageID; // Message ID - data << (uint8) (*itr)->messageType; // Message Type + data << uint16(next_mail_size); // Message size + data << uint32((*itr)->messageID); // Message ID + data << uint8((*itr)->messageType); // Message Type switch((*itr)->messageType) { @@ -623,63 +567,61 @@ void WorldSession::HandleGetMailList(WorldPacket & recv_data ) case MAIL_CREATURE: case MAIL_GAMEOBJECT: case MAIL_AUCTION: - data << (uint32) (*itr)->sender; // creature/gameobject entry, auction id + data << uint32((*itr)->sender); // creature/gameobject entry, auction id break; case MAIL_ITEM: // item entry (?) sender = "Unknown", NYI break; } - data << (uint32) (*itr)->COD; // COD - data << (uint32) (*itr)->itemTextId; // sure about this - data << (uint32) 0; // unknown - data << (uint32) (*itr)->stationery; // stationery (Stationery.dbc) - data << (uint32) (*itr)->money; // Gold - data << (uint32) show_flags; // unknown, 0x4 - auction, 0x10 - normal - // Time - data << (float) ((*itr)->expire_time-time(NULL))/DAY; - data << (uint32) (*itr)->mailTemplateId; // mail template (MailTemplate.dbc) - data << (*itr)->subject; // Subject string - once 00, when mail type = 3 - - data << (uint8) item_count; // client limit is 0x10 + data << uint32((*itr)->COD); // COD + data << uint32((*itr)->itemTextId); // sure about this + data << uint32(0); // unknown + data << uint32((*itr)->stationery); // stationery (Stationery.dbc) + data << uint32((*itr)->money); // Gold + data << uint32(show_flags); // unknown, 0x4 - auction, 0x10 - normal + data << 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 + data << uint8(item_count); // client limit is 0x10 for (uint8 i = 0; i < item_count; ++i) { Item *item = pl->GetMItem((*itr)->items[i].item_guid); // item index (0-6?) - data << (uint8) i; + data << uint8(i); // item guid low? - data << (uint32) (item ? item->GetGUIDLow() : 0); + data << uint32((item ? item->GetGUIDLow() : 0)); // entry - data << (uint32) (item ? item->GetEntry() : 0); + data << uint32((item ? item->GetEntry() : 0)); for (uint8 j = 0; j < MAX_INSPECTED_ENCHANTMENT_SLOT; ++j) { // unsure - data << (uint32) (item ? item->GetEnchantmentCharges((EnchantmentSlot)j) : 0); + data << uint32((item ? item->GetEnchantmentCharges((EnchantmentSlot)j) : 0)); // unsure - data << (uint32) (item ? item->GetEnchantmentDuration((EnchantmentSlot)j) : 0); + data << uint32((item ? item->GetEnchantmentDuration((EnchantmentSlot)j) : 0)); // unsure - data << (uint32) (item ? item->GetEnchantmentId((EnchantmentSlot)j) : 0); + data << uint32((item ? item->GetEnchantmentId((EnchantmentSlot)j) : 0)); } // can be negative - data << (uint32) (item ? item->GetItemRandomPropertyId() : 0); + data << uint32((item ? item->GetItemRandomPropertyId() : 0)); // unk - data << (uint32) (item ? item->GetItemSuffixFactor() : 0); + data << uint32((item ? item->GetItemSuffixFactor() : 0)); // stack count - data << (uint32) (item ? item->GetCount() : 0); + data << uint32((item ? item->GetCount() : 0)); // charges - data << (uint32) (item ? item->GetSpellCharges() : 0); + data << uint32((item ? item->GetSpellCharges() : 0)); // durability - data << (uint32) (item ? item->GetUInt32Value(ITEM_FIELD_MAXDURABILITY) : 0); + data << uint32((item ? item->GetUInt32Value(ITEM_FIELD_MAXDURABILITY) : 0)); // durability - data << (uint32) (item ? item->GetUInt32Value(ITEM_FIELD_DURABILITY) : 0); + data << uint32((item ? item->GetUInt32Value(ITEM_FIELD_DURABILITY) : 0)); // unknown wotlk - data << (uint8) 0; + data << uint8(0); } - mails_count += 1; + mailsCount += 1; } - data.put<uint8>(0, mails_count); // set real send mails to client + data.put<uint8>(0, mailsCount); // set real send mails to client SendPacket(&data); // recalculate m_nextMailDelivereTime and unReadMails @@ -711,7 +653,9 @@ void WorldSession::HandleMailCreateTextItem(WorldPacket & recv_data ) uint64 mailbox; uint32 mailId; - recv_data >> mailbox >> mailId; + recv_data >> mailbox; + recv_data >> mailId; + recv_data.read_skip<uint32>(); // mailTemplateId, non need, Mail store own 100% correct value anyway if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox, GAMEOBJECT_TYPE_MAILBOX)) return; @@ -719,27 +663,42 @@ void WorldSession::HandleMailCreateTextItem(WorldPacket & recv_data ) Player *pl = _player; Mail* m = pl->GetMail(mailId); - if(!m || !m->itemTextId || m->state == MAIL_STATE_DELETED || m->deliver_time > time(NULL)) + if (!m || !m->itemTextId && !m->mailTemplateId || m->state == MAIL_STATE_DELETED || m->deliver_time > time(NULL)) { pl->SendMailResult(mailId, MAIL_MADE_PERMANENT, MAIL_ERR_INTERNAL_ERROR); return; } + uint32 itemTextId = m->itemTextId; + + // in mail template case we need create new text id + if (!itemTextId) + { + MailTemplateEntry const* mailTemplateEntry = sMailTemplateStore.LookupEntry(m->mailTemplateId); + if (!mailTemplateEntry) + { + pl->SendMailResult(mailId, MAIL_MADE_PERMANENT, MAIL_ERR_INTERNAL_ERROR); + return; + } + + itemTextId = objmgr.CreateItemText(mailTemplateEntry->content[GetSessionDbcLocale()]); + } + Item *bodyItem = new Item; // This is not bag and then can be used new Item. - if(!bodyItem->Create(objmgr.GenerateLowGuid(HIGHGUID_ITEM), MAIL_BODY_ITEM_TEMPLATE, pl)) + if (!bodyItem->Create(objmgr.GenerateLowGuid(HIGHGUID_ITEM), MAIL_BODY_ITEM_TEMPLATE, pl)) { delete bodyItem; return; } - bodyItem->SetUInt32Value( ITEM_FIELD_ITEM_TEXT_ID , m->itemTextId ); - bodyItem->SetUInt32Value( ITEM_FIELD_CREATOR, m->sender); + bodyItem->SetUInt32Value(ITEM_FIELD_ITEM_TEXT_ID , itemTextId); + bodyItem->SetUInt32Value(ITEM_FIELD_CREATOR, m->sender); sLog.outDetail("HandleMailCreateTextItem mailid=%u",mailId); ItemPosCountVec dest; - uint8 msg = _player->CanStoreItem( NULL_BAG, NULL_SLOT, dest, bodyItem, false ); - if( msg == EQUIP_ERR_OK ) + uint8 msg = _player->CanStoreItem(NULL_BAG, NULL_SLOT, dest, bodyItem, false); + if (msg == EQUIP_ERR_OK) { m->itemTextId = 0; m->state = MAIL_STATE_CHANGED; @@ -761,145 +720,278 @@ void WorldSession::HandleQueryNextMailTime(WorldPacket & /*recv_data*/ ) { WorldPacket data(MSG_QUERY_NEXT_MAIL_TIME, 8); - if(!_player->m_mailsLoaded) + if (!_player->m_mailsLoaded) _player->_LoadMail(); - if( _player->unReadMails > 0 ) + if (_player->unReadMails > 0) { - data << (uint32) 0; // float - data << (uint32) 0; // count + data << uint32(0); // float + data << uint32(0); // count uint32 count = 0; time_t now = time(NULL); - for (PlayerMails::iterator itr = _player->GetmailBegin(); itr != _player->GetmailEnd(); ++itr) + 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) + if (m->checked & MAIL_CHECK_MASK_READ) continue; // and already delivered - if(now < m->deliver_time) + if (now < m->deliver_time) continue; - data << (uint64) m->sender; // sender guid + data << uint64(m->sender); // sender guid switch(m->messageType) { case MAIL_AUCTION: - data << (uint32) 2; - data << (uint32) 2; - data << (uint32) m->stationery; + data << uint32(2); + data << uint32(2); + data << uint32(m->stationery); break; default: - data << (uint32) 0; - data << (uint32) 0; - data << (uint32) m->stationery; + data << uint32(0); + data << uint32(0); + data << uint32(m->stationery); break; } - data << (uint32) 0xC6000000; // float unk, time or something + data << uint32(0xC6000000); // float unk, time or something ++count; - if(count == 2) // do not display more than 2 mails + if (count == 2) // do not display more than 2 mails break; } data.put<uint32>(4, count); } else { - data << (uint32) 0xC7A8C000; - data << (uint32) 0x00000000; + data << uint32(0xC7A8C000); + data << uint32(0x00000000); } SendPacket(&data); } -void WorldSession::SendMailTo(Player* receiver, uint8 messageType, uint8 stationery, uint32 sender_guidlow_or_entry, uint32 receiver_guidlow, std::string subject, uint32 itemTextId, MailItemsInfo* mi, uint32 money, uint32 COD, uint32 checked, uint32 deliver_delay, uint16 mailTemplateId) +MailSender::MailSender( Object* sender, MailStationery stationery ) : m_stationery(stationery) +{ + switch(sender->GetTypeId()) + { + case TYPEID_UNIT: + m_messageType = MAIL_CREATURE; + m_senderId = sender->GetEntry(); + break; + case TYPEID_GAMEOBJECT: + m_messageType = MAIL_GAMEOBJECT; + m_senderId = sender->GetEntry(); + break; + case TYPEID_ITEM: + m_messageType = MAIL_ITEM; + m_senderId = sender->GetEntry(); + break; + case TYPEID_PLAYER: + m_messageType = MAIL_NORMAL; + m_senderId = sender->GetGUIDLow(); + break; + default: + m_messageType = MAIL_NORMAL; + m_senderId = 0; // will show mail from not existed player + sLog.outError("MailSender::MailSender - Mail have unexpected sender typeid (%u)", sender->GetTypeId()); + break; + } +} + +MailSender::MailSender( AuctionEntry* sender ) + : m_messageType(MAIL_AUCTION), m_senderId(sender->GetHouseId()), m_stationery(MAIL_STATIONERY_AUCTION) +{ +} + + +MailReceiver::MailReceiver( Player* receiver ) : m_receiver(receiver), m_receiver_lowguid(receiver->GetGUIDLow()) +{ +} + +MailReceiver::MailReceiver( Player* receiver,uint32 receiver_lowguid ) : m_receiver(receiver), m_receiver_lowguid(receiver_lowguid) +{ + ASSERT(!receiver || receiver->GetGUIDLow() == receiver_lowguid); +} + +MailDraft& MailDraft::AddItem( Item* item ) +{ + m_items[item->GetGUIDLow()] = item; return *this; +} + +void MailDraft::prepareItems(Player* receiver) { - if (receiver_guidlow == auctionbot.GetAHBplayerGUID()) + if (!m_mailTemplateId || !m_mailTemplateItemsNeed) + return; + + m_mailTemplateItemsNeed = false; + + Loot mailLoot; + + // can be empty + mailLoot.FillLoot(m_mailTemplateId, LootTemplates_Mail, receiver, true, true); + + uint32 max_slot = mailLoot.GetMaxSlotInLootFor(receiver); + for (uint32 i = 0; m_items.size() < MAX_MAIL_ITEMS && i < max_slot; ++i) { - if(messageType == MAIL_AUCTION && mi) // auction mail with items + if (LootItem* lootitem = mailLoot.LootItemInSlot(i,receiver)) { - mi->deleteIncludedItems(true); + if (Item* item = Item::CreateItem(lootitem->itemid,lootitem->count,receiver)) + { + item->SaveToDB(); // save for prevent lost at next mail load, if send fail then item will deleted + AddItem(item); + } } + } +} + +void MailDraft::deleteIncludedItems( bool inDB /*= false*/ ) +{ + for (MailItemMap::iterator mailItemIter = m_items.begin(); mailItemIter != m_items.end(); ++mailItemIter) + { + Item* item = mailItemIter->second; + + if (inDB) + CharacterDatabase.PExecute("DELETE FROM item_instance WHERE guid='%u'", item->GetGUIDLow()); + + delete item; + } + + m_items.clear(); +} + +void MailDraft::SendReturnToSender(uint32 sender_acc, uint32 sender_guid, uint32 receiver_guid ) +{ + Player *receiver = objmgr.GetPlayer(MAKE_NEW_GUID(receiver_guid, 0, HIGHGUID_PLAYER)); + + uint32 rc_account = 0; + if (!receiver) + rc_account = objmgr.GetPlayerAccountIdByGUID(MAKE_NEW_GUID(receiver_guid, 0, HIGHGUID_PLAYER)); + + if (!receiver && !rc_account) // sender not exist + { + deleteIncludedItems(true); return; } + + // prepare mail and send in other case + bool needItemDelay = false; + + if (!m_items.empty()) + { + // if item send to character at another account, then apply item delivery delay + needItemDelay = sender_acc != rc_account; + + // set owner to new receiver (to prevent delete item with sender char deleting) + CharacterDatabase.BeginTransaction(); + for (MailItemMap::iterator mailItemIter = m_items.begin(); mailItemIter != m_items.end(); ++mailItemIter) + { + Item* item = mailItemIter->second; + item->SaveToDB(); // item not in inventory and can be save standalone + // owner in data will set at mail receive and item extracting + CharacterDatabase.PExecute("UPDATE item_instance SET owner_guid = '%u' WHERE guid='%u'", receiver_guid, item->GetGUIDLow()); + } + CharacterDatabase.CommitTransaction(); + } + + // If theres is an item, there is a one hour delivery delay. + uint32 deliver_delay = needItemDelay ? sWorld.getConfig(CONFIG_MAIL_DELIVERY_DELAY) : 0; + + // will delete item or place to receiver mail list + SendMailTo(MailReceiver(receiver,receiver_guid), MailSender(MAIL_NORMAL, sender_guid), MAIL_CHECK_MASK_RETURNED, deliver_delay); +} + +void MailDraft::SendMailTo(MailReceiver const& receiver, MailSender const& sender, MailCheckMask checked, uint32 deliver_delay) +{ + Player* pReceiver = receiver.GetPlayer(); // can be NULL + + if (pReceiver) + prepareItems(pReceiver); // generate mail template items + uint32 mailId = objmgr.GenerateMailID(); + if (receiver.GetPlayerGUIDLow() == auctionbot.GetAHBplayerGUID()) + { + if (sender.GetMailMessageType() == MAIL_AUCTION) // auction mail with items + deleteIncludedItems(true); + return; + } + time_t deliver_time = time(NULL) + deliver_delay; //expire time if COD 3 days, if no COD 30 days, if auction sale pending 1 hour uint32 expire_delay; - if(messageType == MAIL_AUCTION && !mi && !money) // auction mail without any items and money + + // auction mail without any items and money + if (sender.GetMailMessageType() == MAIL_AUCTION && m_items.empty() && !m_money) expire_delay = sWorld.getConfig(CONFIG_MAIL_DELIVERY_DELAY); + // mail from battlemaster (rewardmarks) should last only one day + else if (sender.GetMailMessageType() == MAIL_CREATURE && sBattleGroundMgr.GetBattleMasterBG(sender.GetSenderId()) != BATTLEGROUND_TYPE_NONE) + expire_delay = DAY; + // default case: expire time if COD 3 days, if no COD 30 days else - expire_delay = (COD > 0) ? 3*DAY : 30*DAY; + expire_delay = (m_COD > 0) ? 3 * DAY : 30 * DAY; time_t expire_time = deliver_time + expire_delay; - if(mailTemplateId && !sMailTemplateStore.LookupEntry(mailTemplateId)) + // Add to DB + std::string safe_subject = GetSubject(); + + CharacterDatabase.BeginTransaction(); + CharacterDatabase.escape_string(safe_subject); + CharacterDatabase.PExecute("INSERT INTO mail (id,messageType,stationery,mailTemplateId,sender,receiver,subject,itemTextId,has_items,expire_time,deliver_time,money,cod,checked) " + "VALUES ('%u', '%u', '%u', '%u', '%u', '%u', '%s', '%u', '%u', '" UI64FMTD "','" UI64FMTD "', '%u', '%u', '%d')", + mailId, sender.GetMailMessageType(), sender.GetStationery(), GetMailTemplateId(), sender.GetSenderId(), receiver.GetPlayerGUIDLow(), safe_subject.c_str(), GetBodyId(), (m_items.empty() ? 0 : 1), (uint64)expire_time, (uint64)deliver_time, m_money, m_COD, checked); + + for (MailItemMap::const_iterator mailItemIter = m_items.begin(); mailItemIter != m_items.end(); ++mailItemIter) { - sLog.outError( "WorldSession::SendMailTo - Mail have not existed MailTemplateId (%u), remove at send", mailTemplateId); - mailTemplateId = 0; + Item* item = mailItemIter->second; + CharacterDatabase.PExecute("INSERT INTO mail_items (mail_id,item_guid,item_template,receiver) VALUES ('%u', '%u', '%u','%u')", mailId, item->GetGUIDLow(), item->GetEntry(), receiver.GetPlayerGUIDLow()); } + CharacterDatabase.CommitTransaction(); - if(receiver) + // For online receiver update in game mail status and data + if (pReceiver) { - receiver->AddNewMailDeliverTime(deliver_time); + pReceiver->AddNewMailDeliverTime(deliver_time); - if ( receiver->IsMailsLoaded() ) + if (pReceiver->IsMailsLoaded()) { Mail * m = new Mail; m->messageID = mailId; - m->messageType = messageType; - m->stationery = stationery; - m->mailTemplateId = mailTemplateId; - m->sender = sender_guidlow_or_entry; - m->receiver = receiver->GetGUIDLow(); - m->subject = subject; - m->itemTextId = itemTextId; + m->mailTemplateId = GetMailTemplateId(); + m->subject = GetSubject(); + m->itemTextId = GetBodyId(); + m->money = GetMoney(); + m->COD = GetCOD(); - if(mi) - m->AddAllItems(*mi); + for (MailItemMap::const_iterator mailItemIter = m_items.begin(); mailItemIter != m_items.end(); ++mailItemIter) + { + Item* item = mailItemIter->second; + m->AddItem(item->GetGUIDLow(), item->GetEntry()); + } + m->messageType = sender.GetMailMessageType(); + m->stationery = sender.GetStationery(); + m->sender = sender.GetSenderId(); + m->receiver = receiver.GetPlayerGUIDLow(); m->expire_time = expire_time; m->deliver_time = deliver_time; - m->money = money; - m->COD = COD; m->checked = checked; m->state = MAIL_STATE_UNCHANGED; - receiver->AddMail(m); //to insert new mail to beginning of maillist + pReceiver->AddMail(m); // to insert new mail to beginning of maillist - if(mi) + if (!m_items.empty()) { - for (MailItemMap::iterator mailItemIter = mi->begin(); mailItemIter != mi->end(); ++mailItemIter) - { - MailItem& mailItem = mailItemIter->second; - if(mailItem.item) - receiver->AddMItem(mailItem.item); - } + for (MailItemMap::iterator mailItemIter = m_items.begin(); mailItemIter != m_items.end(); ++mailItemIter) + pReceiver->AddMItem(mailItemIter->second); } } - else if(mi) - mi->deleteIncludedItems(); - } - else if(mi) - mi->deleteIncludedItems(); - - CharacterDatabase.BeginTransaction(); - CharacterDatabase.escape_string(subject); - CharacterDatabase.PExecute("INSERT INTO mail (id,messageType,stationery,mailTemplateId,sender,receiver,subject,itemTextId,has_items,expire_time,deliver_time,money,cod,checked) " - "VALUES ('%u', '%u', '%u', '%u', '%u', '%u', '%s', '%u', '%u', '" UI64FMTD "','" UI64FMTD "', '%u', '%u', '%d')", - mailId, messageType, stationery, mailTemplateId, sender_guidlow_or_entry, receiver_guidlow, subject.c_str(), itemTextId, (mi && !mi->empty() ? 1 : 0), (uint64)expire_time, (uint64)deliver_time, money, COD, checked); - - if(mi) - { - for (MailItemMap::const_iterator mailItemIter = mi->begin(); mailItemIter != mi->end(); ++mailItemIter) - { - MailItem const& mailItem = mailItemIter->second; - CharacterDatabase.PExecute("INSERT INTO mail_items (mail_id,item_guid,item_template,receiver) VALUES ('%u', '%u', '%u','%u')", mailId, mailItem.item_guidlow, mailItem.item_template,receiver_guidlow); - } + else if (!m_items.empty()) + deleteIncludedItems(); } - CharacterDatabase.CommitTransaction(); + else if (!m_items.empty()) + deleteIncludedItems(); } - |