2 * Copyright (C) 2005-2010 MaNGOS <http://getmangos.com/>
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 * This file contains the the code needed for MaNGOS to handle mails.
29 #include "WorldPacket.h"
30 #include "WorldSession.h"
34 #include "ObjectMgr.h"
35 #include "ObjectDefines.h"
37 #include "UpdateMask.h"
40 #include "DBCStores.h"
41 #include "BattleGroundMgr.h"
43 #include "AuctionHouseMgr.h"
45 * Flags that specify special action to be take by the client when displaying this mail.
49 MAIL_SHOW_UNK0
= 0x0001,
50 MAIL_SHOW_DELETE
= 0x0002, ///< forced show of the delete button instead of the return button
51 MAIL_SHOW_AUCTION
= 0x0004, ///< from old comment
52 MAIL_SHOW_UNK2
= 0x0008, ///< unknown, COD will be shown even without that flag
53 MAIL_SHOW_RETURN
= 0x0010,
56 * Handles the Packet sent by the client when sending a mail.
58 * This methods takes the packet sent by the client and performs the following actions:
59 * - Checks whether the mail is valid: i.e. can he send the selected items,
60 * does he have enough money, etc.
61 * - Creates a MailDraft and adds the needed items, money, cost data.
64 * Depending on the outcome of the checks performed the player will recieve a different
67 * @see MailResponseResult
68 * @see SendMailResult()
70 * @param recv_data the WorldPacket containing the data sent by the client.
72 void WorldSession::HandleSendMail(WorldPacket
& recv_data
)
75 std::string receiver
, subject
, body
;
76 uint32 unk1
, unk2
, money
, COD
;
79 recv_data
>> receiver
;
85 recv_data
>> unk1
; // stationery?
86 recv_data
>> unk2
; // 0x00000000
89 recv_data
>> items_count
; // attached items count
91 if (items_count
> MAX_MAIL_ITEMS
) // client limit
93 GetPlayer()->SendMailResult(0, MAIL_SEND
, MAIL_ERR_TOO_MANY_ATTACHMENTS
);
94 recv_data
.rpos(recv_data
.wpos()); // set to end to avoid warnings spam
98 uint64 itemGUIDs
[MAX_MAIL_ITEMS
];
100 for(uint8 i
= 0; i
< items_count
; ++i
)
102 recv_data
.read_skip
<uint8
>(); // item slot in mail, not used
103 recv_data
>> itemGUIDs
[i
];
106 recv_data
>> money
>> COD
; // money and cod
107 recv_data
>> unk3
; // const 0
108 recv_data
>> unk4
; // const 0
110 // packet read complete, now do check
112 if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox
, GAMEOBJECT_TYPE_MAILBOX
))
115 if (receiver
.empty())
118 Player
* pl
= _player
;
121 if (normalizePlayerName(receiver
))
122 rc
= sObjectMgr
.GetPlayerGUIDByName(receiver
);
126 sLog
.outDetail("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",
127 pl
->GetGUIDLow(), receiver
.c_str(), subject
.c_str(), body
.c_str(), items_count
, money
, COD
, unk1
, unk2
);
128 pl
->SendMailResult(0, MAIL_SEND
, MAIL_ERR_RECIPIENT_NOT_FOUND
);
132 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
);
134 if (pl
->GetGUID() == rc
)
136 pl
->SendMailResult(0, MAIL_SEND
, MAIL_ERR_CANNOT_SEND_TO_SELF
);
140 uint32 cost
= items_count
? 30 * items_count
: 30; // price hardcoded in client
142 uint32 reqmoney
= cost
+ money
;
144 if (pl
->GetMoney() < reqmoney
)
146 pl
->SendMailResult(0, MAIL_SEND
, MAIL_ERR_NOT_ENOUGH_MONEY
);
150 Player
*receive
= sObjectMgr
.GetPlayer(rc
);
153 uint8 mails_count
= 0; // do not allow to send to one player more than 100 mails
157 rc_team
= receive
->GetTeam();
158 mails_count
= receive
->GetMailSize();
162 rc_team
= sObjectMgr
.GetPlayerTeamByGUID(rc
);
163 if (QueryResult
* result
= CharacterDatabase
.PQuery("SELECT COUNT(*) FROM mail WHERE receiver = '%u'", GUID_LOPART(rc
)))
165 Field
*fields
= result
->Fetch();
166 mails_count
= fields
[0].GetUInt32();
171 //do not allow to have more than 100 mails in mailbox.. mails count is in opcode uint8!!! - so max can be 255..
172 if (mails_count
> 100)
174 pl
->SendMailResult(0, MAIL_SEND
, MAIL_ERR_RECIPIENT_CAP_REACHED
);
178 // check the receiver's Faction...
179 if (!sWorld
.getConfig(CONFIG_BOOL_ALLOW_TWO_SIDE_INTERACTION_MAIL
) && pl
->GetTeam() != rc_team
&& GetSecurity() == SEC_PLAYER
)
181 pl
->SendMailResult(0, MAIL_SEND
, MAIL_ERR_NOT_YOUR_TEAM
);
185 uint32 rc_account
= receive
186 ? receive
->GetSession()->GetAccountId()
187 : sObjectMgr
.GetPlayerAccountIdByGUID(rc
);
189 Item
* items
[MAX_MAIL_ITEMS
];
191 for(uint8 i
= 0; i
< items_count
; ++i
)
195 pl
->SendMailResult(0, MAIL_SEND
, MAIL_ERR_MAIL_ATTACHMENT_INVALID
);
199 Item
* item
= pl
->GetItemByGuid(itemGUIDs
[i
]);
201 // prevent sending bag with items (cheat: can be placed in bag after adding equipped empty bag to mail)
204 pl
->SendMailResult(0, MAIL_SEND
, MAIL_ERR_MAIL_ATTACHMENT_INVALID
);
208 if (!item
->CanBeTraded(true))
210 pl
->SendMailResult(0, MAIL_SEND
, MAIL_ERR_EQUIP_ERROR
, EQUIP_ERR_MAIL_BOUND_ITEM
);
214 if (item
->IsBoundAccountWide() && item
->IsSoulBound() && pl
->GetSession()->GetAccountId() != rc_account
)
216 pl
->SendMailResult(0, MAIL_SEND
, MAIL_ERR_EQUIP_ERROR
, EQUIP_ERR_ARTEFACTS_ONLY_FOR_OWN_CHARACTERS
);
220 if (item
->HasFlag(ITEM_FIELD_FLAGS
, ITEM_FLAGS_CONJURED
) || item
->GetUInt32Value(ITEM_FIELD_DURATION
))
222 pl
->SendMailResult(0, MAIL_SEND
, MAIL_ERR_EQUIP_ERROR
, EQUIP_ERR_MAIL_BOUND_ITEM
);
226 if (COD
&& item
->HasFlag(ITEM_FIELD_FLAGS
, ITEM_FLAGS_WRAPPED
))
228 pl
->SendMailResult(0, MAIL_SEND
, MAIL_ERR_CANT_SEND_WRAPPED_COD
);
235 pl
->SendMailResult(0, MAIL_SEND
, MAIL_OK
);
237 uint32 itemTextId
= !body
.empty() ? sObjectMgr
.CreateItemText( body
) : 0;
239 pl
->ModifyMoney( -int32(reqmoney
) );
240 pl
->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_GOLD_SPENT_FOR_MAIL
, cost
);
242 bool needItemDelay
= false;
244 MailDraft
draft(subject
, itemTextId
);
246 if (items_count
> 0 || money
> 0)
250 for(uint8 i
= 0; i
< items_count
; ++i
)
252 Item
* item
= items
[i
];
253 if (GetSecurity() > SEC_PLAYER
&& sWorld
.getConfig(CONFIG_BOOL_GM_LOG_TRADE
))
255 sLog
.outCommand(GetAccountId(), "GM %s (Account: %u) mail item: %s (Entry: %u Count: %u) to player: %s (Account: %u)",
256 GetPlayerName(), GetAccountId(), item
->GetProto()->Name1
, item
->GetEntry(), item
->GetCount(), receiver
.c_str(), rc_account
);
259 pl
->MoveItemFromInventory(items
[i
]->GetBagSlot(), item
->GetSlot(), true);
260 CharacterDatabase
.BeginTransaction();
261 item
->DeleteFromInventoryDB(); // deletes item from character's inventory
262 item
->SaveToDB(); // recursive and not have transaction guard into self, item not in inventory and can be save standalone
263 // owner in data will set at mail receive and item extracting
264 CharacterDatabase
.PExecute("UPDATE item_instance SET owner_guid = '%u' WHERE guid='%u'", GUID_LOPART(rc
), item
->GetGUIDLow());
265 CharacterDatabase
.CommitTransaction();
270 // if item send to character at another account, then apply item delivery delay
271 needItemDelay
= pl
->GetSession()->GetAccountId() != rc_account
;
274 if (money
> 0 && GetSecurity() > SEC_PLAYER
&& sWorld
.getConfig(CONFIG_BOOL_GM_LOG_TRADE
))
276 sLog
.outCommand(GetAccountId(),"GM %s (Account: %u) mail money: %u to player: %s (Account: %u)",
277 GetPlayerName(), GetAccountId(), money
, receiver
.c_str(), rc_account
);
281 // If theres is an item, there is a one hour delivery delay if sent to another account's character.
282 uint32 deliver_delay
= needItemDelay
? sWorld
.getConfig(CONFIG_UINT32_MAIL_DELIVERY_DELAY
) : 0;
284 // will delete item or place to receiver mail list
288 .SendMailTo(MailReceiver(receive
, GUID_LOPART(rc
)), pl
, MAIL_CHECK_MASK_NONE
, deliver_delay
);
290 CharacterDatabase
.BeginTransaction();
291 pl
->SaveInventoryAndGoldToDB();
292 CharacterDatabase
.CommitTransaction();
296 * Handles the Packet sent by the client when reading a mail.
298 * This method is called when a client reads a mail that was previously unread.
299 * It will add the MAIL_CHECK_MASK_READ flag to the mail being read.
303 * @param recv_data the packet containing information about the mail the player read.
306 void WorldSession::HandleMailMarkAsRead(WorldPacket
& recv_data
)
310 recv_data
>> mailbox
;
313 if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox
, GAMEOBJECT_TYPE_MAILBOX
))
316 Player
*pl
= _player
;
317 Mail
*m
= pl
->GetMail(mailId
);
322 m
->checked
= m
->checked
| MAIL_CHECK_MASK_READ
;
323 // m->expire_time = time(NULL) + (30 * DAY); // Expire time do not change at reading mail
324 pl
->m_mailsUpdated
= true;
325 m
->state
= MAIL_STATE_CHANGED
;
330 * Handles the Packet sent by the client when deleting a mail.
332 * This method is called when a client deletes a mail in his mailbox.
334 * @param recv_data The packet containing information about the mail being deleted.
337 void WorldSession::HandleMailDelete(WorldPacket
& recv_data
)
341 recv_data
>> mailbox
;
343 recv_data
.read_skip
<uint32
>(); // mailTemplateId
345 if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox
, GAMEOBJECT_TYPE_MAILBOX
))
348 Player
* pl
= _player
;
349 pl
->m_mailsUpdated
= true;
350 Mail
*m
= pl
->GetMail(mailId
);
353 // delete shouldn't show up for COD mails
356 pl
->SendMailResult(mailId
, MAIL_DELETED
, MAIL_ERR_INTERNAL_ERROR
);
360 m
->state
= MAIL_STATE_DELETED
;
362 pl
->SendMailResult(mailId
, MAIL_DELETED
, MAIL_OK
);
365 * Handles the Packet sent by the client when returning a mail to sender.
366 * This method is called when a player chooses to return a mail to its sender.
367 * It will create a new MailDraft and add the items, money, etc. associated with the mail
368 * and then send the mail to the original sender.
370 * @param recv_data The packet containing information about the mail being returned.
373 void WorldSession::HandleMailReturnToSender(WorldPacket
& recv_data
)
377 recv_data
>> mailbox
;
379 recv_data
.read_skip
<uint64
>(); // original sender GUID for return to, not used
381 if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox
, GAMEOBJECT_TYPE_MAILBOX
))
384 Player
*pl
= _player
;
385 Mail
*m
= pl
->GetMail(mailId
);
386 if(!m
|| m
->state
== MAIL_STATE_DELETED
|| m
->deliver_time
> time(NULL
))
388 pl
->SendMailResult(mailId
, MAIL_RETURNED_TO_SENDER
, MAIL_ERR_INTERNAL_ERROR
);
391 //we can return mail now
392 //so firstly delete the old one
393 CharacterDatabase
.BeginTransaction();
394 CharacterDatabase
.PExecute("DELETE FROM mail WHERE id = '%u'", mailId
);
396 CharacterDatabase
.PExecute("DELETE FROM mail_items WHERE mail_id = '%u'", mailId
);
397 CharacterDatabase
.CommitTransaction();
398 pl
->RemoveMail(mailId
);
400 // send back only to players and simple drop for other cases
401 if (m
->messageType
== MAIL_NORMAL
)
403 MailDraft
draft(m
->subject
, m
->itemTextId
);
404 if (m
->mailTemplateId
)
405 draft
= MailDraft(m
->mailTemplateId
,false); // items already included
409 for(std::vector
<MailItemInfo
>::iterator itr2
= m
->items
.begin(); itr2
!= m
->items
.end(); ++itr2
)
411 Item
*item
= pl
->GetMItem(itr2
->item_guid
);
419 pl
->RemoveMItem(itr2
->item_guid
);
423 draft
.AddMoney(m
->money
).SendReturnToSender(GetAccountId(), m
->receiver
, m
->sender
);
426 delete m
; // we can deallocate old mail
427 pl
->SendMailResult(mailId
, MAIL_RETURNED_TO_SENDER
, MAIL_OK
);
431 * Handles the packet sent by the client when taking an item from the mail.
433 void WorldSession::HandleMailTakeItem(WorldPacket
& recv_data
)
438 recv_data
>> mailbox
;
440 recv_data
>> itemId
; // item guid low
442 if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox
, GAMEOBJECT_TYPE_MAILBOX
))
445 Player
* pl
= _player
;
447 Mail
* m
= pl
->GetMail(mailId
);
448 if(!m
|| m
->state
== MAIL_STATE_DELETED
|| m
->deliver_time
> time(NULL
))
450 pl
->SendMailResult(mailId
, MAIL_ITEM_TAKEN
, MAIL_ERR_INTERNAL_ERROR
);
454 // prevent cheating with skip client money check
455 if(pl
->GetMoney() < m
->COD
)
457 pl
->SendMailResult(mailId
, MAIL_ITEM_TAKEN
, MAIL_ERR_NOT_ENOUGH_MONEY
);
461 Item
*it
= pl
->GetMItem(itemId
);
463 ItemPosCountVec dest
;
464 uint8 msg
= _player
->CanStoreItem( NULL_BAG
, NULL_SLOT
, dest
, it
, false );
465 if( msg
== EQUIP_ERR_OK
)
467 m
->RemoveItem(itemId
);
468 m
->removedItems
.push_back(itemId
);
470 if (m
->COD
> 0) // if there is COD, take COD money from player and send them to sender by mail
472 uint64 sender_guid
= MAKE_NEW_GUID(m
->sender
, 0, HIGHGUID_PLAYER
);
473 Player
*receive
= sObjectMgr
.GetPlayer(sender_guid
);
475 uint32 sender_accId
= 0;
477 if( GetSecurity() > SEC_PLAYER
&& sWorld
.getConfig(CONFIG_BOOL_GM_LOG_TRADE
) )
479 std::string sender_name
;
482 sender_accId
= receive
->GetSession()->GetAccountId();
483 sender_name
= receive
->GetName();
487 // can be calculated early
488 sender_accId
= sObjectMgr
.GetPlayerAccountIdByGUID(sender_guid
);
490 if(!sObjectMgr
.GetPlayerNameByGUID(sender_guid
, sender_name
))
491 sender_name
= sObjectMgr
.GetMangosStringForDBCLocale(LANG_UNKNOWN
);
493 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)",
494 GetPlayerName(), GetAccountId(), it
->GetProto()->Name1
, it
->GetEntry(), it
->GetCount(), m
->COD
, sender_name
.c_str(), sender_accId
);
497 sender_accId
= sObjectMgr
.GetPlayerAccountIdByGUID(sender_guid
);
499 // check player existence
500 if(receive
|| sender_accId
)
502 MailDraft(m
->subject
)
504 .SendMailTo(MailReceiver(receive
,m
->sender
),MailSender(MAIL_NORMAL
,m
->receiver
), MAIL_CHECK_MASK_COD_PAYMENT
);
507 pl
->ModifyMoney( -int32(m
->COD
) );
510 m
->state
= MAIL_STATE_CHANGED
;
511 pl
->m_mailsUpdated
= true;
512 pl
->RemoveMItem(it
->GetGUIDLow());
514 uint32 count
= it
->GetCount(); // save counts before store and possible merge with deleting
515 pl
->MoveItemToInventory(dest
, it
, true);
517 CharacterDatabase
.BeginTransaction();
518 pl
->SaveInventoryAndGoldToDB();
520 CharacterDatabase
.CommitTransaction();
522 pl
->SendMailResult(mailId
, MAIL_ITEM_TAKEN
, MAIL_OK
, 0, itemId
, count
);
525 pl
->SendMailResult(mailId
, MAIL_ITEM_TAKEN
, MAIL_ERR_EQUIP_ERROR
, msg
);
528 * Handles the packet sent by the client when taking money from the mail.
530 void WorldSession::HandleMailTakeMoney(WorldPacket
& recv_data
)
534 recv_data
>> mailbox
;
537 if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox
, GAMEOBJECT_TYPE_MAILBOX
))
540 Player
*pl
= _player
;
542 Mail
* m
= pl
->GetMail(mailId
);
543 if(!m
|| m
->state
== MAIL_STATE_DELETED
|| m
->deliver_time
> time(NULL
))
545 pl
->SendMailResult(mailId
, MAIL_MONEY_TAKEN
, MAIL_ERR_INTERNAL_ERROR
);
549 pl
->SendMailResult(mailId
, MAIL_MONEY_TAKEN
, MAIL_OK
);
551 pl
->ModifyMoney(m
->money
);
553 m
->state
= MAIL_STATE_CHANGED
;
554 pl
->m_mailsUpdated
= true;
556 // save money and mail to prevent cheating
557 CharacterDatabase
.BeginTransaction();
560 CharacterDatabase
.CommitTransaction();
564 * Handles the packet sent by the client when requesting the current mail list.
565 * It will send a list of all avaible mails in the players mailbox to the client.
567 void WorldSession::HandleGetMailList(WorldPacket
& recv_data
)
570 recv_data
>> mailbox
;
572 if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox
, GAMEOBJECT_TYPE_MAILBOX
))
575 // client can't work with packets > max int16 value
576 const uint32 maxPacketSize
= 32767;
578 uint32 mailsCount
= 0; // send to client mails amount
579 uint32 realCount
= 0; // real mails amount
581 WorldPacket
data(SMSG_MAIL_LIST_RESULT
, 200); // guess size
582 data
<< uint32(0); // real mail's count
583 data
<< uint8(0); // mail's count
584 time_t cur_time
= time(NULL
);
586 for(PlayerMails::iterator itr
= _player
->GetMailBegin(); itr
!= _player
->GetMailEnd(); ++itr
)
588 // packet send mail count as uint8, prevent overflow
589 if(mailsCount
>= 254)
595 // skip deleted or not delivered (deliver delay not expired) mails
596 if ((*itr
)->state
== MAIL_STATE_DELETED
|| cur_time
< (*itr
)->deliver_time
)
599 uint8 item_count
= (*itr
)->items
.size(); // max count is MAX_MAIL_ITEMS (12)
601 size_t next_mail_size
= 2+4+1+((*itr
)->messageType
== MAIL_NORMAL
? 8 : 4)+4*8+((*itr
)->subject
.size()+1)+1+item_count
*(1+4+4+7*3*4+4+4+4+4+4+4+1);
603 if(data
.wpos()+next_mail_size
> maxPacketSize
)
609 uint32 show_flags
= 0;
610 if ((*itr
)->messageType
!= MAIL_NORMAL
)
611 show_flags
|= MAIL_SHOW_DELETE
;
612 if ((*itr
)->messageType
== MAIL_AUCTION
)
613 show_flags
|= MAIL_SHOW_AUCTION
;
614 if ((*itr
)->HasItems() && (*itr
)->messageType
== MAIL_NORMAL
)
615 show_flags
|= MAIL_SHOW_RETURN
;
617 data
<< uint16(next_mail_size
); // Message size
618 data
<< uint32((*itr
)->messageID
); // Message ID
619 data
<< uint8((*itr
)->messageType
); // Message Type
621 switch((*itr
)->messageType
)
623 case MAIL_NORMAL
: // sender guid
624 data
<< uint64(MAKE_NEW_GUID((*itr
)->sender
, 0, HIGHGUID_PLAYER
));
627 case MAIL_GAMEOBJECT
:
629 data
<< uint32((*itr
)->sender
); // creature/gameobject entry, auction id
631 case MAIL_ITEM
: // item entry (?) sender = "Unknown", NYI
632 data
<< uint32(0); // item entry
636 data
<< uint32((*itr
)->COD
); // COD
637 data
<< uint32((*itr
)->itemTextId
); // sure about this
638 data
<< uint32(0); // unknown
639 data
<< uint32((*itr
)->stationery
); // stationery (Stationery.dbc)
640 data
<< uint32((*itr
)->money
); // Gold
641 data
<< uint32(show_flags
); // unknown, 0x4 - auction, 0x10 - normal
642 data
<< float(((*itr
)->expire_time
-time(NULL
))/DAY
);// Time
643 data
<< uint32((*itr
)->mailTemplateId
); // mail template (MailTemplate.dbc)
644 data
<< (*itr
)->subject
; // Subject string - once 00, when mail type = 3
646 data
<< uint8(item_count
); // client limit is 0x10
648 for(uint8 i
= 0; i
< item_count
; ++i
)
650 Item
*item
= _player
->GetMItem((*itr
)->items
[i
].item_guid
);
654 data
<< uint32(item
? item
->GetGUIDLow() : 0);
656 data
<< uint32(item
? item
->GetEntry() : 0);
657 for(uint8 j
= 0; j
< MAX_INSPECTED_ENCHANTMENT_SLOT
; ++j
)
660 data
<< uint32(item
? item
->GetEnchantmentCharges((EnchantmentSlot
)j
) : 0);
662 data
<< uint32(item
? item
->GetEnchantmentDuration((EnchantmentSlot
)j
) : 0);
664 data
<< uint32(item
? item
->GetEnchantmentId((EnchantmentSlot
)j
) : 0);
667 data
<< uint32(item
? item
->GetItemRandomPropertyId() : 0);
669 data
<< uint32(item
? item
->GetItemSuffixFactor() : 0);
671 data
<< uint32(item
? item
->GetCount() : 0);
673 data
<< uint32(item
? item
->GetSpellCharges() : 0);
675 data
<< uint32(item
? item
->GetUInt32Value(ITEM_FIELD_MAXDURABILITY
) : 0);
677 data
<< uint32(item
? item
->GetUInt32Value(ITEM_FIELD_DURABILITY
) : 0);
686 data
.put
<uint32
>(0, realCount
); // this will display warning about undelivered mail to player if realCount > mailsCount
687 data
.put
<uint8
>(4, mailsCount
); // set real send mails to client
690 // recalculate m_nextMailDelivereTime and unReadMails
691 _player
->UpdateNextMailTimeAndUnreads();
695 * Handles the packet sent by the client when requesting information about the body of a mail.
697 * This function is called when client needs mail message body,
698 * or when player clicks on item which has ITEM_FIELD_ITEM_TEXT_ID > 0
700 void WorldSession::HandleItemTextQuery(WorldPacket
& recv_data
)
703 uint32 mailId
; // this value can be item id in bag, but it is also mail id
704 uint32 unk
; // maybe something like state - 0x70000000
706 recv_data
>> itemTextId
>> mailId
>> unk
;
708 ///TODO: some check needed, if player has item with guid mailId, or has mail with id mailId
710 sLog
.outDebug("CMSG_ITEM_TEXT_QUERY itemguid: %u, mailId: %u, unk: %u", itemTextId
, mailId
, unk
);
712 WorldPacket
data(SMSG_ITEM_TEXT_QUERY_RESPONSE
, (4+10));// guess size
714 data
<< sObjectMgr
.GetItemText( itemTextId
);
719 * Handles the packet sent by the client when he copies the body a mail to his inventory.
721 * When a player copies the body of a mail to his inventory this method is called. It will create
722 * a new item with the text of the mail and store it in the players inventory (if possible).
725 void WorldSession::HandleMailCreateTextItem(WorldPacket
& recv_data
)
730 recv_data
>> mailbox
;
732 recv_data
.read_skip
<uint32
>(); // mailTemplateId, non need, Mail store own 100% correct value anyway
734 if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox
, GAMEOBJECT_TYPE_MAILBOX
))
737 Player
*pl
= _player
;
739 Mail
* m
= pl
->GetMail(mailId
);
740 if (!m
|| (!m
->itemTextId
&& !m
->mailTemplateId
) || m
->state
== MAIL_STATE_DELETED
|| m
->deliver_time
> time(NULL
))
742 pl
->SendMailResult(mailId
, MAIL_MADE_PERMANENT
, MAIL_ERR_INTERNAL_ERROR
);
746 uint32 itemTextId
= m
->itemTextId
;
748 // in mail template case we need create new text id
751 MailTemplateEntry
const* mailTemplateEntry
= sMailTemplateStore
.LookupEntry(m
->mailTemplateId
);
752 if(!mailTemplateEntry
)
754 pl
->SendMailResult(mailId
, MAIL_MADE_PERMANENT
, MAIL_ERR_INTERNAL_ERROR
);
758 itemTextId
= sObjectMgr
.CreateItemText(mailTemplateEntry
->content
[GetSessionDbcLocale()]);
761 Item
*bodyItem
= new Item
; // This is not bag and then can be used new Item.
762 if(!bodyItem
->Create(sObjectMgr
.GenerateLowGuid(HIGHGUID_ITEM
), MAIL_BODY_ITEM_TEMPLATE
, pl
))
768 bodyItem
->SetUInt32Value( ITEM_FIELD_ITEM_TEXT_ID
, itemTextId
);
769 bodyItem
->SetUInt32Value( ITEM_FIELD_CREATOR
, m
->sender
);
771 sLog
.outDetail("HandleMailCreateTextItem mailid=%u",mailId
);
773 ItemPosCountVec dest
;
774 uint8 msg
= _player
->CanStoreItem( NULL_BAG
, NULL_SLOT
, dest
, bodyItem
, false );
775 if( msg
== EQUIP_ERR_OK
)
778 m
->state
= MAIL_STATE_CHANGED
;
779 pl
->m_mailsUpdated
= true;
781 pl
->StoreItem(dest
, bodyItem
, true);
782 //bodyItem->SetState(ITEM_NEW, pl); is set automatically
783 pl
->SendMailResult(mailId
, MAIL_MADE_PERMANENT
, MAIL_OK
);
787 pl
->SendMailResult(mailId
, MAIL_MADE_PERMANENT
, MAIL_ERR_EQUIP_ERROR
, msg
);
793 * No idea when this is called.
795 void WorldSession::HandleQueryNextMailTime(WorldPacket
& /**recv_data*/ )
797 //TODO Fix me! ... this void has probably bad condition, but good data are sent
798 WorldPacket
data(MSG_QUERY_NEXT_MAIL_TIME
, 8);
800 if( _player
->unReadMails
> 0 )
802 data
<< uint32(0); // float
803 data
<< uint32(0); // count
806 time_t now
= time(NULL
);
807 for(PlayerMails::iterator itr
= _player
->GetMailBegin(); itr
!= _player
->GetMailEnd(); ++itr
)
810 // must be not checked yet
811 if(m
->checked
& MAIL_CHECK_MASK_READ
)
814 // and already delivered
815 if(now
< m
->deliver_time
)
818 data
<< uint64(m
->sender
); // sender guid
820 switch(m
->messageType
)
823 data
<< uint32(m
->sender
); // auction house id
824 data
<< uint32(MAIL_AUCTION
); // message type
832 data
<< uint32(m
->stationery
);
833 data
<< uint32(0xC6000000); // float unk, time or something
836 if(count
== 2) // do not display more than 2 mails
839 data
.put
<uint32
>(4, count
);
843 data
<< uint32(0xC7A8C000);
844 data
<< uint32(0x00000000);
850 * Creates a new MailSender object.
852 * @param sender The object/player sending this mail.
853 * @param stationery The stationary associated with this sender.
855 MailSender::MailSender( Object
* sender
, MailStationery stationery
) : m_stationery(stationery
)
857 switch(sender
->GetTypeId())
860 m_messageType
= MAIL_CREATURE
;
861 m_senderId
= sender
->GetEntry();
863 case TYPEID_GAMEOBJECT
:
864 m_messageType
= MAIL_GAMEOBJECT
;
865 m_senderId
= sender
->GetEntry();
868 m_messageType
= MAIL_ITEM
;
869 m_senderId
= sender
->GetEntry();
872 m_messageType
= MAIL_NORMAL
;
873 m_senderId
= sender
->GetGUIDLow();
876 m_messageType
= MAIL_NORMAL
;
877 m_senderId
= 0; // will show mail from not existed player
878 sLog
.outError( "MailSender::MailSender - Mail have unexpected sender typeid (%u)", sender
->GetTypeId());
883 * Creates a new MailSender object from an AuctionEntry.
885 * @param sender the AuctionEntry from which this mail is generated.
887 MailSender::MailSender( AuctionEntry
* sender
)
888 : m_messageType(MAIL_AUCTION
), m_senderId(sender
->GetHouseId()), m_stationery(MAIL_STATIONERY_AUCTION
)
893 * Creates a new MailReceiver object.
895 * @param receiver The player receiving the mail.
897 MailReceiver::MailReceiver( Player
* receiver
) : m_receiver(receiver
), m_receiver_lowguid(receiver
->GetGUIDLow())
901 * Creates a new MailReceiver object with a specified GUID.
903 * @param receiver The player receiving the mail.
904 * @param receiver_lowguid The GUID to use instead of the receivers.
906 MailReceiver::MailReceiver( Player
* receiver
,uint32 receiver_lowguid
) : m_receiver(receiver
), m_receiver_lowguid(receiver_lowguid
)
908 ASSERT(!receiver
|| receiver
->GetGUIDLow() == receiver_lowguid
);
911 * Adds an item to the MailDraft.
913 * @param item The item to be added to the MailDraft.
914 * @returns the MailDraft the item was added to.
916 MailDraft
& MailDraft::AddItem( Item
* item
)
918 m_items
[item
->GetGUIDLow()] = item
; return *this;
921 * Prepares the items in a MailDraft.
923 void MailDraft::prepareItems(Player
* receiver
)
925 if (!m_mailTemplateId
|| !m_mailTemplateItemsNeed
)
928 m_mailTemplateItemsNeed
= false;
933 mailLoot
.FillLoot(m_mailTemplateId
, LootTemplates_Mail
, receiver
, true, true);
935 uint32 max_slot
= mailLoot
.GetMaxSlotInLootFor(receiver
);
936 for(uint32 i
= 0; m_items
.size() < MAX_MAIL_ITEMS
&& i
< max_slot
; ++i
)
938 if (LootItem
* lootitem
= mailLoot
.LootItemInSlot(i
,receiver
))
940 if (Item
* item
= Item::CreateItem(lootitem
->itemid
,lootitem
->count
,receiver
))
942 item
->SaveToDB(); // save for prevent lost at next mail load, if send fail then item will deleted
949 * Deletes the items included in a MailDraft.
951 * @param inDB A boolean specifying whether the change should be saved to the database or not.
953 void MailDraft::deleteIncludedItems( bool inDB
/**= false*/ )
955 for(MailItemMap::iterator mailItemIter
= m_items
.begin(); mailItemIter
!= m_items
.end(); ++mailItemIter
)
957 Item
* item
= mailItemIter
->second
;
960 CharacterDatabase
.PExecute("DELETE FROM item_instance WHERE guid='%u'", item
->GetGUIDLow());
968 * Returns a mail to its sender.
969 * @param sender_acc The id of the account of the sender.
970 * @param sender_guid The low part of the GUID of the sender.
971 * @param receiver_guid The low part of the GUID of the reciever.
973 void MailDraft::SendReturnToSender(uint32 sender_acc
, uint32 sender_guid
, uint32 receiver_guid
)
975 Player
*receiver
= sObjectMgr
.GetPlayer(MAKE_NEW_GUID(receiver_guid
, 0, HIGHGUID_PLAYER
));
977 uint32 rc_account
= 0;
979 rc_account
= sObjectMgr
.GetPlayerAccountIdByGUID(MAKE_NEW_GUID(receiver_guid
, 0, HIGHGUID_PLAYER
));
981 if(!receiver
&& !rc_account
) // sender not exist
983 deleteIncludedItems(true);
987 // prepare mail and send in other case
988 bool needItemDelay
= false;
992 // if item send to character at another account, then apply item delivery delay
993 needItemDelay
= sender_acc
!= rc_account
;
995 // set owner to new receiver (to prevent delete item with sender char deleting)
996 CharacterDatabase
.BeginTransaction();
997 for(MailItemMap::iterator mailItemIter
= m_items
.begin(); mailItemIter
!= m_items
.end(); ++mailItemIter
)
999 Item
* item
= mailItemIter
->second
;
1000 item
->SaveToDB(); // item not in inventory and can be save standalone
1001 // owner in data will set at mail receive and item extracting
1002 CharacterDatabase
.PExecute("UPDATE item_instance SET owner_guid = '%u' WHERE guid='%u'", receiver_guid
, item
->GetGUIDLow());
1004 CharacterDatabase
.CommitTransaction();
1007 // If theres is an item, there is a one hour delivery delay.
1008 uint32 deliver_delay
= needItemDelay
? sWorld
.getConfig(CONFIG_UINT32_MAIL_DELIVERY_DELAY
) : 0;
1010 // will delete item or place to receiver mail list
1011 SendMailTo(MailReceiver(receiver
,receiver_guid
), MailSender(MAIL_NORMAL
, sender_guid
), MAIL_CHECK_MASK_RETURNED
, deliver_delay
);
1016 * @param receiver The MailReceiver to which this mail is sent.
1017 * @param sender The MailSender from which this mail is originated.
1018 * @param checked The mask used to specify the mail.
1019 * @param deliver_delay The delay after which the mail is delivered in seconds
1021 void MailDraft::SendMailTo(MailReceiver
const& receiver
, MailSender
const& sender
, MailCheckMask checked
, uint32 deliver_delay
)
1023 Player
* pReceiver
= receiver
.GetPlayer(); // can be NULL
1026 prepareItems(pReceiver
); // generate mail template items
1029 uint32 mailId
= sObjectMgr
.GenerateMailID();
1031 time_t deliver_time
= time(NULL
) + deliver_delay
;
1033 uint32 expire_delay
;
1034 // auction mail without any items and money (auction sale note) pending 1 hour
1035 if (sender
.GetMailMessageType() == MAIL_AUCTION
&& m_items
.empty() && !m_money
)
1036 expire_delay
= HOUR
;
1037 // mail from battlemaster (rewardmarks) should last only one day
1038 else if (sender
.GetMailMessageType() == MAIL_CREATURE
&& sBattleGroundMgr
.GetBattleMasterBG(sender
.GetSenderId()) != BATTLEGROUND_TYPE_NONE
)
1040 // default case: expire time if COD 3 days, if no COD 30 days
1042 expire_delay
= (m_COD
> 0) ? 3 * DAY
: 30 * DAY
;
1044 time_t expire_time
= deliver_time
+ expire_delay
;
1047 std::string safe_subject
= GetSubject();
1049 CharacterDatabase
.BeginTransaction();
1050 CharacterDatabase
.escape_string(safe_subject
);
1051 CharacterDatabase
.PExecute("INSERT INTO mail (id,messageType,stationery,mailTemplateId,sender,receiver,subject,itemTextId,has_items,expire_time,deliver_time,money,cod,checked) "
1052 "VALUES ('%u', '%u', '%u', '%u', '%u', '%u', '%s', '%u', '%u', '" UI64FMTD
"','" UI64FMTD
"', '%u', '%u', '%d')",
1053 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
);
1055 for(MailItemMap::const_iterator mailItemIter
= m_items
.begin(); mailItemIter
!= m_items
.end(); ++mailItemIter
)
1057 Item
* item
= mailItemIter
->second
;
1058 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());
1060 CharacterDatabase
.CommitTransaction();
1062 // For online receiver update in game mail status and data
1065 pReceiver
->AddNewMailDeliverTime(deliver_time
);
1068 m
->messageID
= mailId
;
1069 m
->mailTemplateId
= GetMailTemplateId();
1070 m
->subject
= GetSubject();
1071 m
->itemTextId
= GetBodyId();
1072 m
->money
= GetMoney();
1075 for(MailItemMap::const_iterator mailItemIter
= m_items
.begin(); mailItemIter
!= m_items
.end(); ++mailItemIter
)
1077 Item
* item
= mailItemIter
->second
;
1078 m
->AddItem(item
->GetGUIDLow(), item
->GetEntry());
1081 m
->messageType
= sender
.GetMailMessageType();
1082 m
->stationery
= sender
.GetStationery();
1083 m
->sender
= sender
.GetSenderId();
1084 m
->receiver
= receiver
.GetPlayerGUIDLow();
1085 m
->expire_time
= expire_time
;
1086 m
->deliver_time
= deliver_time
;
1087 m
->checked
= checked
;
1088 m
->state
= MAIL_STATE_UNCHANGED
;
1090 pReceiver
->AddMail(m
); // to insert new mail to beginning of maillist
1092 if (!m_items
.empty())
1094 for(MailItemMap::iterator mailItemIter
= m_items
.begin(); mailItemIter
!= m_items
.end(); ++mailItemIter
)
1095 pReceiver
->AddMItem(mailItemIter
->second
);
1098 else if (!m_items
.empty())
1099 deleteIncludedItems();