[9411] More SpellEffectIndex using in apropriate cases
[getmangos.git] / src / game / Mail.cpp
blob4bd86422772911071496889429c6f753d5efb204
1 /*
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
19 /**
20 * @addtogroup mailing
21 * @{
23 * @file Mail.cpp
24 * This file contains the the code needed for MaNGOS to handle mails.
28 #include "Mail.h"
29 #include "WorldPacket.h"
30 #include "WorldSession.h"
31 #include "Opcodes.h"
32 #include "Log.h"
33 #include "World.h"
34 #include "ObjectMgr.h"
35 #include "ObjectDefines.h"
36 #include "Player.h"
37 #include "UpdateMask.h"
38 #include "Unit.h"
39 #include "Language.h"
40 #include "DBCStores.h"
41 #include "BattleGroundMgr.h"
42 #include "Item.h"
43 #include "AuctionHouseMgr.h"
44 /**
45 * Flags that specify special action to be take by the client when displaying this mail.
47 enum MailShowFlags
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,
55 /**
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.
62 * - Sends the mail.
64 * Depending on the outcome of the checks performed the player will recieve a different
65 * MailResponseResult.
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 )
74 uint64 mailbox, unk3;
75 std::string receiver, subject, body;
76 uint32 unk1, unk2, money, COD;
77 uint8 unk4;
78 recv_data >> mailbox;
79 recv_data >> receiver;
81 recv_data >> subject;
83 recv_data >> body;
85 recv_data >> unk1; // stationery?
86 recv_data >> unk2; // 0x00000000
88 uint8 items_count;
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
95 return;
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))
113 return;
115 if (receiver.empty())
116 return;
118 Player* pl = _player;
120 uint64 rc = 0;
121 if (normalizePlayerName(receiver))
122 rc = sObjectMgr.GetPlayerGUIDByName(receiver);
124 if (!rc)
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);
129 return;
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);
137 return;
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);
147 return;
150 Player *receive = sObjectMgr.GetPlayer(rc);
152 uint32 rc_team = 0;
153 uint8 mails_count = 0; // do not allow to send to one player more than 100 mails
155 if (receive)
157 rc_team = receive->GetTeam();
158 mails_count = receive->GetMailSize();
160 else
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();
167 delete result;
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);
175 return;
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);
182 return;
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)
193 if (!itemGUIDs[i])
195 pl->SendMailResult(0, MAIL_SEND, MAIL_ERR_MAIL_ATTACHMENT_INVALID);
196 return;
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)
202 if(!item)
204 pl->SendMailResult(0, MAIL_SEND, MAIL_ERR_MAIL_ATTACHMENT_INVALID);
205 return;
208 if (!item->CanBeTraded(true))
210 pl->SendMailResult(0, MAIL_SEND, MAIL_ERR_EQUIP_ERROR, EQUIP_ERR_MAIL_BOUND_ITEM);
211 return;
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);
217 return;
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);
223 return;
226 if (COD && item->HasFlag(ITEM_FIELD_FLAGS, ITEM_FLAGS_WRAPPED))
228 pl->SendMailResult(0, MAIL_SEND, MAIL_ERR_CANT_SEND_WRAPPED_COD);
229 return;
232 items[i] = item;
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)
248 if (items_count > 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();
267 draft.AddItem(item);
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
285 draft
286 .AddMoney(money)
287 .AddCOD(COD)
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.
301 * @see MailCheckMask
303 * @param recv_data the packet containing information about the mail the player read.
306 void WorldSession::HandleMailMarkAsRead(WorldPacket & recv_data )
308 uint64 mailbox;
309 uint32 mailId;
310 recv_data >> mailbox;
311 recv_data >> mailId;
313 if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox, GAMEOBJECT_TYPE_MAILBOX))
314 return;
316 Player *pl = _player;
317 Mail *m = pl->GetMail(mailId);
318 if (m)
320 if (pl->unReadMails)
321 --pl->unReadMails;
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 )
339 uint64 mailbox;
340 uint32 mailId;
341 recv_data >> mailbox;
342 recv_data >> mailId;
343 recv_data.read_skip<uint32>(); // mailTemplateId
345 if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox, GAMEOBJECT_TYPE_MAILBOX))
346 return;
348 Player* pl = _player;
349 pl->m_mailsUpdated = true;
350 Mail *m = pl->GetMail(mailId);
351 if(m)
353 // delete shouldn't show up for COD mails
354 if (m->COD)
356 pl->SendMailResult(mailId, MAIL_DELETED, MAIL_ERR_INTERNAL_ERROR);
357 return;
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 )
375 uint64 mailbox;
376 uint32 mailId;
377 recv_data >> mailbox;
378 recv_data >> mailId;
379 recv_data.read_skip<uint64>(); // original sender GUID for return to, not used
381 if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox, GAMEOBJECT_TYPE_MAILBOX))
382 return;
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);
389 return;
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);
395 // needed?
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
407 if(m->HasItems())
409 for(std::vector<MailItemInfo>::iterator itr2 = m->items.begin(); itr2 != m->items.end(); ++itr2)
411 Item *item = pl->GetMItem(itr2->item_guid);
412 if(item)
413 draft.AddItem(item);
414 else
416 //WTF?
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 )
435 uint64 mailbox;
436 uint32 mailId;
437 uint32 itemId;
438 recv_data >> mailbox;
439 recv_data >> mailId;
440 recv_data >> itemId; // item guid low
442 if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox, GAMEOBJECT_TYPE_MAILBOX))
443 return;
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);
451 return;
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);
458 return;
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;
480 if(receive)
482 sender_accId = receive->GetSession()->GetAccountId();
483 sender_name = receive->GetName();
485 else
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);
496 else if(!receive)
497 sender_accId = sObjectMgr.GetPlayerAccountIdByGUID(sender_guid);
499 // check player existence
500 if(receive || sender_accId)
502 MailDraft(m->subject)
503 .AddMoney(m->COD)
504 .SendMailTo(MailReceiver(receive,m->sender),MailSender(MAIL_NORMAL,m->receiver), MAIL_CHECK_MASK_COD_PAYMENT);
507 pl->ModifyMoney( -int32(m->COD) );
509 m->COD = 0;
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();
519 pl->_SaveMail();
520 CharacterDatabase.CommitTransaction();
522 pl->SendMailResult(mailId, MAIL_ITEM_TAKEN, MAIL_OK, 0, itemId, count);
524 else
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 )
532 uint64 mailbox;
533 uint32 mailId;
534 recv_data >> mailbox;
535 recv_data >> mailId;
537 if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox, GAMEOBJECT_TYPE_MAILBOX))
538 return;
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);
546 return;
549 pl->SendMailResult(mailId, MAIL_MONEY_TAKEN, MAIL_OK);
551 pl->ModifyMoney(m->money);
552 m->money = 0;
553 m->state = MAIL_STATE_CHANGED;
554 pl->m_mailsUpdated = true;
556 // save money and mail to prevent cheating
557 CharacterDatabase.BeginTransaction();
558 pl->SaveGoldToDB();
559 pl->_SaveMail();
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 )
569 uint64 mailbox;
570 recv_data >> mailbox;
572 if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox, GAMEOBJECT_TYPE_MAILBOX))
573 return;
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)
591 realCount += 1;
592 continue;
595 // skip deleted or not delivered (deliver delay not expired) mails
596 if ((*itr)->state == MAIL_STATE_DELETED || cur_time < (*itr)->deliver_time)
597 continue;
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)
605 realCount += 1;
606 continue;
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));
625 break;
626 case MAIL_CREATURE:
627 case MAIL_GAMEOBJECT:
628 case MAIL_AUCTION:
629 data << uint32((*itr)->sender); // creature/gameobject entry, auction id
630 break;
631 case MAIL_ITEM: // item entry (?) sender = "Unknown", NYI
632 data << uint32(0); // item entry
633 break;
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);
651 // item index (0-6?)
652 data << uint8(i);
653 // item guid low?
654 data << uint32(item ? item->GetGUIDLow() : 0);
655 // entry
656 data << uint32(item ? item->GetEntry() : 0);
657 for(uint8 j = 0; j < MAX_INSPECTED_ENCHANTMENT_SLOT; ++j)
659 // unsure
660 data << uint32(item ? item->GetEnchantmentCharges((EnchantmentSlot)j) : 0);
661 // unsure
662 data << uint32(item ? item->GetEnchantmentDuration((EnchantmentSlot)j) : 0);
663 // unsure
664 data << uint32(item ? item->GetEnchantmentId((EnchantmentSlot)j) : 0);
666 // can be negative
667 data << uint32(item ? item->GetItemRandomPropertyId() : 0);
668 // unk
669 data << uint32(item ? item->GetItemSuffixFactor() : 0);
670 // stack count
671 data << uint32(item ? item->GetCount() : 0);
672 // charges
673 data << uint32(item ? item->GetSpellCharges() : 0);
674 // durability
675 data << uint32(item ? item->GetUInt32Value(ITEM_FIELD_MAXDURABILITY) : 0);
676 // durability
677 data << uint32(item ? item->GetUInt32Value(ITEM_FIELD_DURABILITY) : 0);
678 // unknown wotlk
679 data << uint8(0);
682 mailsCount += 1;
683 realCount += 1;
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
688 SendPacket(&data);
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 )
702 uint32 itemTextId;
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
713 data << itemTextId;
714 data << sObjectMgr.GetItemText( itemTextId );
715 SendPacket(&data);
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 )
727 uint64 mailbox;
728 uint32 mailId;
730 recv_data >> mailbox;
731 recv_data >> mailId;
732 recv_data.read_skip<uint32>(); // mailTemplateId, non need, Mail store own 100% correct value anyway
734 if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox, GAMEOBJECT_TYPE_MAILBOX))
735 return;
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);
743 return;
746 uint32 itemTextId = m->itemTextId;
748 // in mail template case we need create new text id
749 if(!itemTextId)
751 MailTemplateEntry const* mailTemplateEntry = sMailTemplateStore.LookupEntry(m->mailTemplateId);
752 if(!mailTemplateEntry)
754 pl->SendMailResult(mailId, MAIL_MADE_PERMANENT, MAIL_ERR_INTERNAL_ERROR);
755 return;
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))
764 delete bodyItem;
765 return;
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 )
777 m->itemTextId = 0;
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);
785 else
787 pl->SendMailResult(mailId, MAIL_MADE_PERMANENT, MAIL_ERR_EQUIP_ERROR, msg);
788 delete bodyItem;
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
805 uint32 count = 0;
806 time_t now = time(NULL);
807 for(PlayerMails::iterator itr = _player->GetMailBegin(); itr != _player->GetMailEnd(); ++itr)
809 Mail *m = (*itr);
810 // must be not checked yet
811 if(m->checked & MAIL_CHECK_MASK_READ)
812 continue;
814 // and already delivered
815 if(now < m->deliver_time)
816 continue;
818 data << uint64(m->sender); // sender guid
820 switch(m->messageType)
822 case MAIL_AUCTION:
823 data << uint32(m->sender); // auction house id
824 data << uint32(MAIL_AUCTION); // message type
825 break;
826 default:
827 data << uint32(0);
828 data << uint32(0);
829 break;
832 data << uint32(m->stationery);
833 data << uint32(0xC6000000); // float unk, time or something
835 ++count;
836 if(count == 2) // do not display more than 2 mails
837 break;
839 data.put<uint32>(4, count);
841 else
843 data << uint32(0xC7A8C000);
844 data << uint32(0x00000000);
846 SendPacket(&data);
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())
859 case TYPEID_UNIT:
860 m_messageType = MAIL_CREATURE;
861 m_senderId = sender->GetEntry();
862 break;
863 case TYPEID_GAMEOBJECT:
864 m_messageType = MAIL_GAMEOBJECT;
865 m_senderId = sender->GetEntry();
866 break;
867 case TYPEID_ITEM:
868 m_messageType = MAIL_ITEM;
869 m_senderId = sender->GetEntry();
870 break;
871 case TYPEID_PLAYER:
872 m_messageType = MAIL_NORMAL;
873 m_senderId = sender->GetGUIDLow();
874 break;
875 default:
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());
879 break;
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)
926 return;
928 m_mailTemplateItemsNeed = false;
930 Loot mailLoot;
932 // can be empty
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
943 AddItem(item);
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;
959 if(inDB)
960 CharacterDatabase.PExecute("DELETE FROM item_instance WHERE guid='%u'", item->GetGUIDLow());
962 delete item;
965 m_items.clear();
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;
978 if(!receiver)
979 rc_account = sObjectMgr.GetPlayerAccountIdByGUID(MAKE_NEW_GUID(receiver_guid, 0, HIGHGUID_PLAYER));
981 if(!receiver && !rc_account) // sender not exist
983 deleteIncludedItems(true);
984 return;
987 // prepare mail and send in other case
988 bool needItemDelay = false;
990 if(!m_items.empty())
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);
1014 * Sends a mail.
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
1025 if (pReceiver)
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)
1039 expire_delay = DAY;
1040 // default case: expire time if COD 3 days, if no COD 30 days
1041 else
1042 expire_delay = (m_COD > 0) ? 3 * DAY : 30 * DAY;
1044 time_t expire_time = deliver_time + expire_delay;
1046 // Add to DB
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
1063 if (pReceiver)
1065 pReceiver->AddNewMailDeliverTime(deliver_time);
1067 Mail *m = new Mail;
1068 m->messageID = mailId;
1069 m->mailTemplateId = GetMailTemplateId();
1070 m->subject = GetSubject();
1071 m->itemTextId = GetBodyId();
1072 m->money = GetMoney();
1073 m->COD = GetCOD();
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();
1101 /*! @} */