2 * Copyright (C) 2005-2009 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
20 #include "WorldPacket.h"
21 #include "WorldSession.h"
25 #include "ObjectMgr.h"
27 #include "UpdateMask.h"
30 #include "DBCStores.h"
32 void MailItem::deleteItem( bool inDB
)
37 CharacterDatabase
.PExecute("DELETE FROM item_instance WHERE guid='%u'", item
->GetGUIDLow());
44 void WorldSession::HandleSendMail(WorldPacket
& recv_data
)
46 CHECK_PACKET_SIZE(recv_data
,8+1+1+1+4+4+1+4+4+8+1);
49 std::string receiver
, subject
, body
;
50 uint32 unk1
, unk2
, money
, COD
;
53 recv_data
>> receiver
;
55 if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox
, GAMEOBJECT_TYPE_MAILBOX
))
59 CHECK_PACKET_SIZE(recv_data
, 8+(receiver
.size()+1)+1+1+4+4+1+4+4+8+1);
64 CHECK_PACKET_SIZE(recv_data
, 8+(receiver
.size()+1)+(subject
.size()+1)+1+4+4+1+4+4+8+1);
69 CHECK_PACKET_SIZE(recv_data
, 8+(receiver
.size()+1)+(subject
.size()+1)+(body
.size()+1)+4+4+1+4+4+8+1);
71 recv_data
>> unk1
; // stationery?
72 recv_data
>> unk2
; // 0x00000000
77 recv_data
>> items_count
; // attached items count
79 if(items_count
> 12) // client limit
81 GetPlayer()->SendMailResult(0, MAIL_SEND
, MAIL_ERR_TOO_MANY_ATTACHMENTS
);
86 CHECK_PACKET_SIZE(recv_data
, 8+(receiver
.size()+1)+(subject
.size()+1)+(body
.size()+1)+4+4+1+items_count
*(1+8)+4+4+8+1);
90 for(uint8 i
= 0; i
< items_count
; ++i
)
94 recv_data
>> item_slot
;
95 recv_data
>> item_guid
;
96 mi
.AddItem(GUID_LOPART(item_guid
), item_slot
);
100 recv_data
>> money
>> COD
; // money and cod
101 recv_data
>> unk3
; // const 0
102 recv_data
>> unk4
; // const 0
104 items_count
= mi
.size(); // this is the real size after the duplicates have been removed
106 if (receiver
.empty())
109 Player
* pl
= _player
;
112 if(normalizePlayerName(receiver
))
113 rc
= objmgr
.GetPlayerGUIDByName(receiver
);
117 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",
118 pl
->GetGUIDLow(), receiver
.c_str(), subject
.c_str(), body
.c_str(), items_count
, money
, COD
, unk1
, unk2
);
119 pl
->SendMailResult(0, MAIL_SEND
, MAIL_ERR_RECIPIENT_NOT_FOUND
);
123 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
);
125 if(pl
->GetGUID() == rc
)
127 pl
->SendMailResult(0, MAIL_SEND
, MAIL_ERR_CANNOT_SEND_TO_SELF
);
131 uint32 cost
= items_count
? 30 * items_count
: 30; // price hardcoded in client
133 uint32 reqmoney
= cost
+ money
;
135 if (pl
->GetMoney() < reqmoney
)
137 pl
->SendMailResult(0, MAIL_SEND
, MAIL_ERR_NOT_ENOUGH_MONEY
);
141 Player
*receive
= objmgr
.GetPlayer(rc
);
144 uint8 mails_count
= 0; //do not allow to send to one player more than 100 mails
148 rc_team
= receive
->GetTeam();
149 mails_count
= receive
->GetMailSize();
153 rc_team
= objmgr
.GetPlayerTeamByGUID(rc
);
154 QueryResult
* result
= CharacterDatabase
.PQuery("SELECT COUNT(*) FROM mail WHERE receiver = '%u'", GUID_LOPART(rc
));
157 Field
*fields
= result
->Fetch();
158 mails_count
= fields
[0].GetUInt32();
162 //do not allow to have more than 100 mails in mailbox.. mails count is in opcode uint8!!! - so max can be 255..
163 if (mails_count
> 100)
165 pl
->SendMailResult(0, MAIL_SEND
, MAIL_ERR_RECIPIENT_CAP_REACHED
);
168 // test the receiver's Faction...
169 if (!sWorld
.getConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_MAIL
) && pl
->GetTeam() != rc_team
&& GetSecurity() == SEC_PLAYER
)
171 pl
->SendMailResult(0, MAIL_SEND
, MAIL_ERR_NOT_YOUR_TEAM
);
175 uint32 rc_account
= 0;
177 rc_account
= receive
->GetSession()->GetAccountId();
179 rc_account
= objmgr
.GetPlayerAccountIdByGUID(rc
);
183 for(MailItemMap::iterator mailItemIter
= mi
.begin(); mailItemIter
!= mi
.end(); ++mailItemIter
)
185 MailItem
& mailItem
= mailItemIter
->second
;
187 if(!mailItem
.item_guidlow
)
189 pl
->SendMailResult(0, MAIL_SEND
, MAIL_ERR_MAIL_ATTACHMENT_INVALID
);
193 mailItem
.item
= pl
->GetItemByGuid(MAKE_NEW_GUID(mailItem
.item_guidlow
, 0, HIGHGUID_ITEM
));
194 // prevent sending bag with items (cheat: can be placed in bag after adding equipped empty bag to mail)
197 pl
->SendMailResult(0, MAIL_SEND
, MAIL_ERR_MAIL_ATTACHMENT_INVALID
);
201 if(!mailItem
.item
->CanBeTraded(true))
203 pl
->SendMailResult(0, MAIL_SEND
, MAIL_ERR_EQUIP_ERROR
, EQUIP_ERR_MAIL_BOUND_ITEM
);
207 if(mailItem
.item
->IsBoundAccountWide() && mailItem
.item
->IsSoulBound() && pl
->GetSession()->GetAccountId() != rc_account
)
209 pl
->SendMailResult(0, MAIL_SEND
, MAIL_ERR_EQUIP_ERROR
, EQUIP_ERR_ARTEFACTS_ONLY_FOR_OWN_CHARACTERS
);
213 if (mailItem
.item
->HasFlag(ITEM_FIELD_FLAGS
, ITEM_FLAGS_CONJURED
) || mailItem
.item
->GetUInt32Value(ITEM_FIELD_DURATION
))
215 pl
->SendMailResult(0, MAIL_SEND
, MAIL_ERR_EQUIP_ERROR
, EQUIP_ERR_MAIL_BOUND_ITEM
);
219 if(COD
&& mailItem
.item
->HasFlag(ITEM_FIELD_FLAGS
, ITEM_FLAGS_WRAPPED
))
221 pl
->SendMailResult(0, MAIL_SEND
, MAIL_ERR_CANT_SEND_WRAPPED_COD
);
226 pl
->SendMailResult(0, MAIL_SEND
, MAIL_OK
);
228 uint32 itemTextId
= 0;
231 itemTextId
= objmgr
.CreateItemText( body
);
234 pl
->ModifyMoney( -int32(reqmoney
) );
235 pl
->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_GOLD_SPENT_FOR_MAIL
, cost
);
237 bool needItemDelay
= false;
239 if(items_count
> 0 || money
> 0)
243 for(MailItemMap::iterator mailItemIter
= mi
.begin(); mailItemIter
!= mi
.end(); ++mailItemIter
)
245 MailItem
& mailItem
= mailItemIter
->second
;
249 mailItem
.item_template
= mailItem
.item
? mailItem
.item
->GetEntry() : 0;
251 if( GetSecurity() > SEC_PLAYER
&& sWorld
.getConfig(CONFIG_GM_LOG_TRADE
) )
253 sLog
.outCommand(GetAccountId(), "GM %s (Account: %u) mail item: %s (Entry: %u Count: %u) to player: %s (Account: %u)",
254 GetPlayerName(), GetAccountId(), mailItem
.item
->GetProto()->Name1
, mailItem
.item
->GetEntry(), mailItem
.item
->GetCount(), receiver
.c_str(), rc_account
);
257 pl
->MoveItemFromInventory(mailItem
.item
->GetBagSlot(), mailItem
.item
->GetSlot(), true);
258 CharacterDatabase
.BeginTransaction();
259 mailItem
.item
->DeleteFromInventoryDB(); //deletes item from character's inventory
260 mailItem
.item
->SaveToDB(); // recursive and not have transaction guard into self, item not in inventory and can be save standalone
261 // owner in data will set at mail receive and item extracting
262 CharacterDatabase
.PExecute("UPDATE item_instance SET owner_guid = '%u' WHERE guid='%u'", GUID_LOPART(rc
), mailItem
.item
->GetGUIDLow());
263 CharacterDatabase
.CommitTransaction();
266 // if item send to character at another account, then apply item delivery delay
267 needItemDelay
= pl
->GetSession()->GetAccountId() != rc_account
;
270 if(money
> 0 && GetSecurity() > SEC_PLAYER
&& sWorld
.getConfig(CONFIG_GM_LOG_TRADE
))
272 sLog
.outCommand(GetAccountId(),"GM %s (Account: %u) mail money: %u to player: %s (Account: %u)",
273 GetPlayerName(), GetAccountId(), money
, receiver
.c_str(), rc_account
);
277 // If theres is an item, there is a one hour delivery delay if sent to another account's character.
278 uint32 deliver_delay
= needItemDelay
? sWorld
.getConfig(CONFIG_MAIL_DELIVERY_DELAY
) : 0;
280 // will delete item or place to receiver mail list
281 WorldSession::SendMailTo(receive
, MAIL_NORMAL
, MAIL_STATIONERY_NORMAL
, pl
->GetGUIDLow(), GUID_LOPART(rc
), subject
, itemTextId
, &mi
, money
, COD
, MAIL_CHECK_MASK_NONE
, deliver_delay
);
283 CharacterDatabase
.BeginTransaction();
284 pl
->SaveInventoryAndGoldToDB();
285 CharacterDatabase
.CommitTransaction();
288 //called when mail is read
289 void WorldSession::HandleMailMarkAsRead(WorldPacket
& recv_data
)
291 CHECK_PACKET_SIZE(recv_data
,8+4);
295 recv_data
>> mailbox
;
297 if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox
, GAMEOBJECT_TYPE_MAILBOX
))
301 Player
*pl
= _player
;
302 Mail
*m
= pl
->GetMail(mailId
);
307 m
->checked
= m
->checked
| MAIL_CHECK_MASK_READ
;
308 // m->expire_time = time(NULL) + (30 * DAY); // Expire time do not change at reading mail
309 pl
->m_mailsUpdated
= true;
310 m
->state
= MAIL_STATE_CHANGED
;
314 //called when client deletes mail
315 void WorldSession::HandleMailDelete(WorldPacket
& recv_data
)
317 CHECK_PACKET_SIZE(recv_data
,8+4);
321 recv_data
>> mailbox
;
324 if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox
, GAMEOBJECT_TYPE_MAILBOX
))
327 Player
* pl
= _player
;
328 pl
->m_mailsUpdated
= true;
329 Mail
*m
= pl
->GetMail(mailId
);
331 m
->state
= MAIL_STATE_DELETED
;
332 pl
->SendMailResult(mailId
, MAIL_DELETED
, MAIL_OK
);
335 void WorldSession::HandleMailReturnToSender(WorldPacket
& recv_data
)
337 CHECK_PACKET_SIZE(recv_data
,8+4);
341 recv_data
>> mailbox
;
343 if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox
, GAMEOBJECT_TYPE_MAILBOX
))
347 Player
*pl
= _player
;
348 Mail
*m
= pl
->GetMail(mailId
);
349 if(!m
|| m
->state
== MAIL_STATE_DELETED
|| m
->deliver_time
> time(NULL
))
351 pl
->SendMailResult(mailId
, MAIL_RETURNED_TO_SENDER
, MAIL_ERR_INTERNAL_ERROR
);
354 //we can return mail now
355 //so firstly delete the old one
356 CharacterDatabase
.BeginTransaction();
357 CharacterDatabase
.PExecute("DELETE FROM mail WHERE id = '%u'", mailId
);
359 CharacterDatabase
.PExecute("DELETE FROM mail_items WHERE mail_id = '%u'", mailId
);
360 CharacterDatabase
.CommitTransaction();
361 pl
->RemoveMail(mailId
);
367 for(std::vector
<MailItemInfo
>::iterator itr2
= m
->items
.begin(); itr2
!= m
->items
.end(); ++itr2
)
369 Item
*item
= pl
->GetMItem(itr2
->item_guid
);
371 mi
.AddItem(item
->GetGUIDLow(), item
->GetEntry(), item
);
377 pl
->RemoveMItem(itr2
->item_guid
);
381 SendReturnToSender(MAIL_NORMAL
, GetAccountId(), m
->receiver
, m
->sender
, m
->subject
, m
->itemTextId
, &mi
, m
->money
, m
->mailTemplateId
);
383 delete m
; //we can deallocate old mail
384 pl
->SendMailResult(mailId
, MAIL_RETURNED_TO_SENDER
, MAIL_OK
);
387 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
)
389 if(messageType
!= MAIL_NORMAL
) // return only to players
391 mi
->deleteIncludedItems(true);
395 Player
*receiver
= objmgr
.GetPlayer(MAKE_NEW_GUID(receiver_guid
, 0, HIGHGUID_PLAYER
));
397 uint32 rc_account
= 0;
399 rc_account
= objmgr
.GetPlayerAccountIdByGUID(MAKE_NEW_GUID(receiver_guid
, 0, HIGHGUID_PLAYER
));
401 if(!receiver
&& !rc_account
) // sender not exist
403 mi
->deleteIncludedItems(true);
407 // prepare mail and send in other case
408 bool needItemDelay
= false;
410 if(mi
&& !mi
->empty())
412 // if item send to character at another account, then apply item delivery delay
413 needItemDelay
= sender_acc
!= rc_account
;
415 // set owner to new receiver (to prevent delete item with sender char deleting)
416 CharacterDatabase
.BeginTransaction();
417 for(MailItemMap::iterator mailItemIter
= mi
->begin(); mailItemIter
!= mi
->end(); ++mailItemIter
)
419 MailItem
& mailItem
= mailItemIter
->second
;
420 mailItem
.item
->SaveToDB(); // item not in inventory and can be save standalone
421 // owner in data will set at mail receive and item extracting
422 CharacterDatabase
.PExecute("UPDATE item_instance SET owner_guid = '%u' WHERE guid='%u'", receiver_guid
, mailItem
.item
->GetGUIDLow());
424 CharacterDatabase
.CommitTransaction();
427 // If theres is an item, there is a one hour delivery delay.
428 uint32 deliver_delay
= needItemDelay
? sWorld
.getConfig(CONFIG_MAIL_DELIVERY_DELAY
) : 0;
430 // will delete item or place to receiver mail list
431 WorldSession::SendMailTo(receiver
, MAIL_NORMAL
, MAIL_STATIONERY_NORMAL
, sender_guid
, receiver_guid
, subject
, itemTextId
, mi
, money
, 0, MAIL_CHECK_MASK_RETURNED
,deliver_delay
,mailTemplateId
);
434 //called when player takes item attached in mail
435 void WorldSession::HandleMailTakeItem(WorldPacket
& recv_data
)
437 CHECK_PACKET_SIZE(recv_data
,8+4+4);
442 recv_data
>> mailbox
;
444 if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox
, GAMEOBJECT_TYPE_MAILBOX
))
448 recv_data
>> itemId
; // item guid low?
449 Player
* pl
= _player
;
451 Mail
* m
= pl
->GetMail(mailId
);
452 if(!m
|| m
->state
== MAIL_STATE_DELETED
|| m
->deliver_time
> time(NULL
))
454 pl
->SendMailResult(mailId
, MAIL_ITEM_TAKEN
, MAIL_ERR_INTERNAL_ERROR
);
458 // prevent cheating with skip client money check
459 if(pl
->GetMoney() < m
->COD
)
461 pl
->SendMailResult(mailId
, MAIL_ITEM_TAKEN
, MAIL_ERR_NOT_ENOUGH_MONEY
);
465 Item
*it
= pl
->GetMItem(itemId
);
467 ItemPosCountVec dest
;
468 uint8 msg
= _player
->CanStoreItem( NULL_BAG
, NULL_SLOT
, dest
, it
, false );
469 if( msg
== EQUIP_ERR_OK
)
471 m
->RemoveItem(itemId
);
472 m
->removedItems
.push_back(itemId
);
474 if (m
->COD
> 0) //if there is COD, take COD money from player and send them to sender by mail
476 uint64 sender_guid
= MAKE_NEW_GUID(m
->sender
, 0, HIGHGUID_PLAYER
);
477 Player
*receive
= objmgr
.GetPlayer(sender_guid
);
479 uint32 sender_accId
= 0;
481 if( GetSecurity() > SEC_PLAYER
&& sWorld
.getConfig(CONFIG_GM_LOG_TRADE
) )
483 std::string sender_name
;
486 sender_accId
= receive
->GetSession()->GetAccountId();
487 sender_name
= receive
->GetName();
491 // can be calculated early
492 sender_accId
= objmgr
.GetPlayerAccountIdByGUID(sender_guid
);
494 if(!objmgr
.GetPlayerNameByGUID(sender_guid
,sender_name
))
495 sender_name
= objmgr
.GetMangosStringForDBCLocale(LANG_UNKNOWN
);
497 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)",
498 GetPlayerName(),GetAccountId(),it
->GetProto()->Name1
,it
->GetEntry(),it
->GetCount(),m
->COD
,sender_name
.c_str(),sender_accId
);
501 sender_accId
= objmgr
.GetPlayerAccountIdByGUID(sender_guid
);
503 // check player existence
504 if(receive
|| sender_accId
)
506 WorldSession::SendMailTo(receive
, MAIL_NORMAL
, MAIL_STATIONERY_NORMAL
, m
->receiver
, m
->sender
, m
->subject
, 0, NULL
, m
->COD
, 0, MAIL_CHECK_MASK_COD_PAYMENT
);
509 pl
->ModifyMoney( -int32(m
->COD
) );
512 m
->state
= MAIL_STATE_CHANGED
;
513 pl
->m_mailsUpdated
= true;
514 pl
->RemoveMItem(it
->GetGUIDLow());
516 uint32 count
= it
->GetCount(); // save counts before store and possible merge with deleting
517 pl
->MoveItemToInventory(dest
,it
,true);
519 CharacterDatabase
.BeginTransaction();
520 pl
->SaveInventoryAndGoldToDB();
522 CharacterDatabase
.CommitTransaction();
524 pl
->SendMailResult(mailId
, MAIL_ITEM_TAKEN
, MAIL_OK
, 0, itemId
, count
);
527 pl
->SendMailResult(mailId
, MAIL_ITEM_TAKEN
, MAIL_ERR_EQUIP_ERROR
, msg
);
530 void WorldSession::HandleMailTakeMoney(WorldPacket
& recv_data
)
532 CHECK_PACKET_SIZE(recv_data
,8+4);
536 recv_data
>> mailbox
;
539 if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox
, GAMEOBJECT_TYPE_MAILBOX
))
542 Player
*pl
= _player
;
544 Mail
* m
= pl
->GetMail(mailId
);
545 if(!m
|| m
->state
== MAIL_STATE_DELETED
|| m
->deliver_time
> time(NULL
))
547 pl
->SendMailResult(mailId
, MAIL_MONEY_TAKEN
, MAIL_ERR_INTERNAL_ERROR
);
551 pl
->SendMailResult(mailId
, MAIL_MONEY_TAKEN
, MAIL_OK
);
553 pl
->ModifyMoney(m
->money
);
555 m
->state
= MAIL_STATE_CHANGED
;
556 pl
->m_mailsUpdated
= true;
558 // save money and mail to prevent cheating
559 CharacterDatabase
.BeginTransaction();
560 pl
->SaveDataFieldToDB(); // contains money
562 CharacterDatabase
.CommitTransaction();
565 //called when player lists his received mails
566 void WorldSession::HandleGetMailList(WorldPacket
& recv_data
)
568 CHECK_PACKET_SIZE(recv_data
,8);
571 recv_data
>> mailbox
;
573 if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox
, GAMEOBJECT_TYPE_MAILBOX
))
576 Player
* pl
= _player
;
578 //load players mails, and mailed items
579 if(!pl
->m_mailsLoaded
)
582 // client can't work with packets > max int16 value
583 const uint32 maxPacketSize
= 32767;
585 uint32 mails_count
= 0; // real send to client mails amount
587 WorldPacket
data(SMSG_MAIL_LIST_RESULT
, (200)); // guess size
588 data
<< uint8(0); // mail's count
589 time_t cur_time
= time(NULL
);
591 for(PlayerMails::iterator itr
= pl
->GetmailBegin(); itr
!= pl
->GetmailEnd(); ++itr
)
593 // skip deleted or not delivered (deliver delay not expired) mails
594 if ((*itr
)->state
== MAIL_STATE_DELETED
|| cur_time
< (*itr
)->deliver_time
)
597 uint8 item_count
= (*itr
)->items
.size(); // max count is MAX_MAIL_ITEMS (12)
599 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);
601 if(data
.wpos()+next_mail_size
> maxPacketSize
)
604 data
<< (uint16
) 0x0040; // unknown 2.3.0, different values
605 data
<< (uint32
) (*itr
)->messageID
; // Message ID
606 data
<< (uint8
) (*itr
)->messageType
; // Message Type
608 switch((*itr
)->messageType
)
610 case MAIL_NORMAL
: // sender guid
611 data
<< uint64(MAKE_NEW_GUID((*itr
)->sender
, 0, HIGHGUID_PLAYER
));
614 case MAIL_GAMEOBJECT
:
616 data
<< (uint32
) (*itr
)->sender
; // creature/gameobject entry, auction id
618 case MAIL_ITEM
: // item entry (?) sender = "Unknown", NYI
622 data
<< (uint32
) (*itr
)->COD
; // COD
623 data
<< (uint32
) (*itr
)->itemTextId
; // sure about this
624 data
<< (uint32
) 0; // unknown
625 data
<< (uint32
) (*itr
)->stationery
; // stationery (Stationery.dbc)
626 data
<< (uint32
) (*itr
)->money
; // Gold
627 data
<< (uint32
) 0x04; // unknown, 0x4 - auction, 0x10 - normal
629 data
<< (float) ((*itr
)->expire_time
-time(NULL
))/DAY
;
630 data
<< (uint32
) (*itr
)->mailTemplateId
; // mail template (MailTemplate.dbc)
631 data
<< (*itr
)->subject
; // Subject string - once 00, when mail type = 3
633 data
<< (uint8
) item_count
; // client limit is 0x10
635 for(uint8 i
= 0; i
< item_count
; ++i
)
637 Item
*item
= pl
->GetMItem((*itr
)->items
[i
].item_guid
);
641 data
<< (uint32
) (item
? item
->GetGUIDLow() : 0);
643 data
<< (uint32
) (item
? item
->GetEntry() : 0);
644 for(uint8 j
= 0; j
< MAX_INSPECTED_ENCHANTMENT_SLOT
; ++j
)
647 data
<< (uint32
) (item
? item
->GetEnchantmentCharges((EnchantmentSlot
)j
) : 0);
649 data
<< (uint32
) (item
? item
->GetEnchantmentDuration((EnchantmentSlot
)j
) : 0);
651 data
<< (uint32
) (item
? item
->GetEnchantmentId((EnchantmentSlot
)j
) : 0);
654 data
<< (uint32
) (item
? item
->GetItemRandomPropertyId() : 0);
656 data
<< (uint32
) (item
? item
->GetItemSuffixFactor() : 0);
658 data
<< (uint32
) (item
? item
->GetCount() : 0);
660 data
<< (uint32
) (item
? item
->GetSpellCharges() : 0);
662 data
<< (uint32
) (item
? item
->GetUInt32Value(ITEM_FIELD_MAXDURABILITY
) : 0);
664 data
<< (uint32
) (item
? item
->GetUInt32Value(ITEM_FIELD_DURABILITY
) : 0);
672 data
.put
<uint8
>(0, mails_count
); // set real send mails to client
675 // recalculate m_nextMailDelivereTime and unReadMails
676 _player
->UpdateNextMailTimeAndUnreads();
679 ///this function is called when client needs mail message body, or when player clicks on item which has ITEM_FIELD_ITEM_TEXT_ID > 0
680 void WorldSession::HandleItemTextQuery(WorldPacket
& recv_data
)
682 CHECK_PACKET_SIZE(recv_data
,4+4+4);
685 uint32 mailId
; //this value can be item id in bag, but it is also mail id
686 uint32 unk
; //maybe something like state - 0x70000000
688 recv_data
>> itemTextId
>> mailId
>> unk
;
690 //some check needed, if player has item with guid mailId, or has mail with id mailId
692 sLog
.outDebug("CMSG_ITEM_TEXT_QUERY itemguid: %u, mailId: %u, unk: %u", itemTextId
, mailId
, unk
);
694 WorldPacket
data(SMSG_ITEM_TEXT_QUERY_RESPONSE
, (4+10));// guess size
696 data
<< objmgr
.GetItemText( itemTextId
);
700 //used when player copies mail body to his inventory
701 void WorldSession::HandleMailCreateTextItem(WorldPacket
& recv_data
)
703 CHECK_PACKET_SIZE(recv_data
,8+4);
708 recv_data
>> mailbox
>> mailId
;
710 if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox
, GAMEOBJECT_TYPE_MAILBOX
))
713 Player
*pl
= _player
;
715 Mail
* m
= pl
->GetMail(mailId
);
716 if(!m
|| !m
->itemTextId
|| m
->state
== MAIL_STATE_DELETED
|| m
->deliver_time
> time(NULL
))
718 pl
->SendMailResult(mailId
, MAIL_MADE_PERMANENT
, MAIL_ERR_INTERNAL_ERROR
);
722 Item
*bodyItem
= new Item
; // This is not bag and then can be used new Item.
723 if(!bodyItem
->Create(objmgr
.GenerateLowGuid(HIGHGUID_ITEM
), MAIL_BODY_ITEM_TEMPLATE
, pl
))
729 bodyItem
->SetUInt32Value( ITEM_FIELD_ITEM_TEXT_ID
, m
->itemTextId
);
730 bodyItem
->SetUInt32Value( ITEM_FIELD_CREATOR
, m
->sender
);
732 sLog
.outDetail("HandleMailCreateTextItem mailid=%u",mailId
);
734 ItemPosCountVec dest
;
735 uint8 msg
= _player
->CanStoreItem( NULL_BAG
, NULL_SLOT
, dest
, bodyItem
, false );
736 if( msg
== EQUIP_ERR_OK
)
739 m
->state
= MAIL_STATE_CHANGED
;
740 pl
->m_mailsUpdated
= true;
742 pl
->StoreItem(dest
, bodyItem
, true);
743 //bodyItem->SetState(ITEM_NEW, pl); is set automatically
744 pl
->SendMailResult(mailId
, MAIL_MADE_PERMANENT
, MAIL_OK
);
748 pl
->SendMailResult(mailId
, MAIL_MADE_PERMANENT
, MAIL_ERR_EQUIP_ERROR
, msg
);
753 //TODO Fix me! ... this void has probably bad condition, but good data are sent
754 void WorldSession::HandleQueryNextMailTime(WorldPacket
& /*recv_data*/ )
756 WorldPacket
data(MSG_QUERY_NEXT_MAIL_TIME
, 8);
758 if(!_player
->m_mailsLoaded
)
759 _player
->_LoadMail();
761 if( _player
->unReadMails
> 0 )
763 data
<< (uint32
) 0; // float
764 data
<< (uint32
) 0; // count
766 for(PlayerMails::iterator itr
= _player
->GetmailBegin(); itr
!= _player
->GetmailEnd(); ++itr
)
769 // not checked yet, already must be delivered
770 if((m
->checked
& MAIL_CHECK_MASK_READ
)==0 && (m
->deliver_time
<= time(NULL
)))
780 data
<< (uint64
) m
->sender
; // sender guid
782 switch(m
->messageType
)
787 data
<< (uint32
) m
->stationery
;
792 data
<< (uint32
) m
->stationery
;
795 data
<< (uint32
) 0xC6000000; // float unk, time or something
798 data
.put
<uint32
>(4, count
);
802 data
<< (uint32
) 0xC7A8C000;
803 data
<< (uint32
) 0x00000000;
808 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
)
810 uint32 mailId
= objmgr
.GenerateMailID();
812 time_t deliver_time
= time(NULL
) + deliver_delay
;
814 //expire time if COD 3 days, if no COD 30 days, if auction sale pending 1 hour
816 if(messageType
== MAIL_AUCTION
&& !mi
&& !money
) // auction mail without any items and money
819 expire_delay
= (COD
> 0) ? 3*DAY
: 30*DAY
;
821 time_t expire_time
= deliver_time
+ expire_delay
;
823 if(mailTemplateId
&& !sMailTemplateStore
.LookupEntry(mailTemplateId
))
825 sLog
.outError( "WorldSession::SendMailTo - Mail have not existed MailTemplateId (%u), remove at send", mailTemplateId
);
831 receiver
->AddNewMailDeliverTime(deliver_time
);
833 if ( receiver
->IsMailsLoaded() )
836 m
->messageID
= mailId
;
837 m
->messageType
= messageType
;
838 m
->stationery
= stationery
;
839 m
->mailTemplateId
= mailTemplateId
;
840 m
->sender
= sender_guidlow_or_entry
;
841 m
->receiver
= receiver
->GetGUIDLow();
842 m
->subject
= subject
;
843 m
->itemTextId
= itemTextId
;
848 m
->expire_time
= expire_time
;
849 m
->deliver_time
= deliver_time
;
852 m
->checked
= checked
;
853 m
->state
= MAIL_STATE_UNCHANGED
;
855 receiver
->AddMail(m
); //to insert new mail to beginning of maillist
859 for(MailItemMap::iterator mailItemIter
= mi
->begin(); mailItemIter
!= mi
->end(); ++mailItemIter
)
861 MailItem
& mailItem
= mailItemIter
->second
;
863 receiver
->AddMItem(mailItem
.item
);
868 mi
->deleteIncludedItems();
871 mi
->deleteIncludedItems();
873 CharacterDatabase
.BeginTransaction();
874 CharacterDatabase
.escape_string(subject
);
875 CharacterDatabase
.PExecute("INSERT INTO mail (id,messageType,stationery,mailTemplateId,sender,receiver,subject,itemTextId,has_items,expire_time,deliver_time,money,cod,checked) "
876 "VALUES ('%u', '%u', '%u', '%u', '%u', '%u', '%s', '%u', '%u', '" I64FMTD
"','" I64FMTD
"', '%u', '%u', '%d')",
877 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
);
881 for(MailItemMap::const_iterator mailItemIter
= mi
->begin(); mailItemIter
!= mi
->end(); ++mailItemIter
)
883 MailItem
const& mailItem
= mailItemIter
->second
;
884 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
);
887 CharacterDatabase
.CommitTransaction();