[9192] Fixed typo in error output for lock id gameobject template data check.
[getmangos.git] / src / game / Mail.cpp
blob0509240f6b0d7c7fff2646dadafed84be9197e0b
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 #include "Mail.h"
20 #include "WorldPacket.h"
21 #include "WorldSession.h"
22 #include "Opcodes.h"
23 #include "Log.h"
24 #include "World.h"
25 #include "ObjectMgr.h"
26 #include "ObjectDefines.h"
27 #include "Player.h"
28 #include "UpdateMask.h"
29 #include "Unit.h"
30 #include "Language.h"
31 #include "DBCStores.h"
32 #include "BattleGroundMgr.h"
33 #include "Item.h"
34 #include "AuctionHouseMgr.h"
36 enum MailShowFlags
38 MAIL_SHOW_UNK0 = 0x0001,
39 MAIL_SHOW_DELETE = 0x0002, // forced show delete button instead return button
40 MAIL_SHOW_AUCTION = 0x0004, // from old comment
41 MAIL_SHOW_UNK2 = 0x0008, // unknown, COD will be shown even without that flag
42 MAIL_SHOW_RETURN = 0x0010,
45 void WorldSession::HandleSendMail(WorldPacket & recv_data )
47 uint64 mailbox, unk3;
48 std::string receiver, subject, body;
49 uint32 unk1, unk2, money, COD;
50 uint8 unk4;
51 recv_data >> mailbox;
52 recv_data >> receiver;
54 recv_data >> subject;
56 recv_data >> body;
58 recv_data >> unk1; // stationery?
59 recv_data >> unk2; // 0x00000000
61 uint8 items_count;
62 recv_data >> items_count; // attached items count
64 if (items_count > MAX_MAIL_ITEMS) // client limit
66 GetPlayer()->SendMailResult(0, MAIL_SEND, MAIL_ERR_TOO_MANY_ATTACHMENTS);
67 recv_data.rpos(recv_data.wpos()); // set to end to avoid warnings spam
68 return;
71 uint64 itemGUIDs[MAX_MAIL_ITEMS];
73 for(uint8 i = 0; i < items_count; ++i)
75 recv_data.read_skip<uint8>(); // item slot in mail, not used
76 recv_data >> itemGUIDs[i];
79 recv_data >> money >> COD; // money and cod
80 recv_data >> unk3; // const 0
81 recv_data >> unk4; // const 0
83 // packet read complete, now do check
85 if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox, GAMEOBJECT_TYPE_MAILBOX))
86 return;
88 if (receiver.empty())
89 return;
91 Player* pl = _player;
93 uint64 rc = 0;
94 if (normalizePlayerName(receiver))
95 rc = sObjectMgr.GetPlayerGUIDByName(receiver);
97 if (!rc)
99 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",
100 pl->GetGUIDLow(), receiver.c_str(), subject.c_str(), body.c_str(), items_count, money, COD, unk1, unk2);
101 pl->SendMailResult(0, MAIL_SEND, MAIL_ERR_RECIPIENT_NOT_FOUND);
102 return;
105 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);
107 if (pl->GetGUID() == rc)
109 pl->SendMailResult(0, MAIL_SEND, MAIL_ERR_CANNOT_SEND_TO_SELF);
110 return;
113 uint32 cost = items_count ? 30 * items_count : 30; // price hardcoded in client
115 uint32 reqmoney = cost + money;
117 if (pl->GetMoney() < reqmoney)
119 pl->SendMailResult(0, MAIL_SEND, MAIL_ERR_NOT_ENOUGH_MONEY);
120 return;
123 Player *receive = sObjectMgr.GetPlayer(rc);
125 uint32 rc_team = 0;
126 uint8 mails_count = 0; // do not allow to send to one player more than 100 mails
128 if (receive)
130 rc_team = receive->GetTeam();
131 mails_count = receive->GetMailSize();
133 else
135 rc_team = sObjectMgr.GetPlayerTeamByGUID(rc);
136 if (QueryResult* result = CharacterDatabase.PQuery("SELECT COUNT(*) FROM mail WHERE receiver = '%u'", GUID_LOPART(rc)))
138 Field *fields = result->Fetch();
139 mails_count = fields[0].GetUInt32();
140 delete result;
144 //do not allow to have more than 100 mails in mailbox.. mails count is in opcode uint8!!! - so max can be 255..
145 if (mails_count > 100)
147 pl->SendMailResult(0, MAIL_SEND, MAIL_ERR_RECIPIENT_CAP_REACHED);
148 return;
151 // check the receiver's Faction...
152 if (!sWorld.getConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_MAIL) && pl->GetTeam() != rc_team && GetSecurity() == SEC_PLAYER)
154 pl->SendMailResult(0, MAIL_SEND, MAIL_ERR_NOT_YOUR_TEAM);
155 return;
158 uint32 rc_account = receive
159 ? receive->GetSession()->GetAccountId()
160 : sObjectMgr.GetPlayerAccountIdByGUID(rc);
162 Item* items[MAX_MAIL_ITEMS];
164 for(uint8 i = 0; i < items_count; ++i)
166 if (!itemGUIDs[i])
168 pl->SendMailResult(0, MAIL_SEND, MAIL_ERR_MAIL_ATTACHMENT_INVALID);
169 return;
172 Item* item = pl->GetItemByGuid(itemGUIDs[i]);
174 // prevent sending bag with items (cheat: can be placed in bag after adding equipped empty bag to mail)
175 if(!item)
177 pl->SendMailResult(0, MAIL_SEND, MAIL_ERR_MAIL_ATTACHMENT_INVALID);
178 return;
181 if (!item->CanBeTraded(true))
183 pl->SendMailResult(0, MAIL_SEND, MAIL_ERR_EQUIP_ERROR, EQUIP_ERR_MAIL_BOUND_ITEM);
184 return;
187 if (item->IsBoundAccountWide() && item->IsSoulBound() && pl->GetSession()->GetAccountId() != rc_account)
189 pl->SendMailResult(0, MAIL_SEND, MAIL_ERR_EQUIP_ERROR, EQUIP_ERR_ARTEFACTS_ONLY_FOR_OWN_CHARACTERS);
190 return;
193 if (item->HasFlag(ITEM_FIELD_FLAGS, ITEM_FLAGS_CONJURED) || item->GetUInt32Value(ITEM_FIELD_DURATION))
195 pl->SendMailResult(0, MAIL_SEND, MAIL_ERR_EQUIP_ERROR, EQUIP_ERR_MAIL_BOUND_ITEM);
196 return;
199 if (COD && item->HasFlag(ITEM_FIELD_FLAGS, ITEM_FLAGS_WRAPPED))
201 pl->SendMailResult(0, MAIL_SEND, MAIL_ERR_CANT_SEND_WRAPPED_COD);
202 return;
205 items[i] = item;
208 pl->SendMailResult(0, MAIL_SEND, MAIL_OK);
210 uint32 itemTextId = !body.empty() ? sObjectMgr.CreateItemText( body ) : 0;
212 pl->ModifyMoney( -int32(reqmoney) );
213 pl->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_GOLD_SPENT_FOR_MAIL, cost);
215 bool needItemDelay = false;
217 MailDraft draft(subject, itemTextId);
219 if (items_count > 0 || money > 0)
221 if (items_count > 0)
223 for(uint8 i = 0; i < items_count; ++i)
225 Item* item = items[i];
226 if (GetSecurity() > SEC_PLAYER && sWorld.getConfig(CONFIG_GM_LOG_TRADE))
228 sLog.outCommand(GetAccountId(), "GM %s (Account: %u) mail item: %s (Entry: %u Count: %u) to player: %s (Account: %u)",
229 GetPlayerName(), GetAccountId(), item->GetProto()->Name1, item->GetEntry(), item->GetCount(), receiver.c_str(), rc_account);
232 pl->MoveItemFromInventory(items[i]->GetBagSlot(), item->GetSlot(), true);
233 CharacterDatabase.BeginTransaction();
234 item->DeleteFromInventoryDB(); // deletes item from character's inventory
235 item->SaveToDB(); // recursive and not have transaction guard into self, item not in inventory and can be save standalone
236 // owner in data will set at mail receive and item extracting
237 CharacterDatabase.PExecute("UPDATE item_instance SET owner_guid = '%u' WHERE guid='%u'", GUID_LOPART(rc), item->GetGUIDLow());
238 CharacterDatabase.CommitTransaction();
240 draft.AddItem(item);
243 // if item send to character at another account, then apply item delivery delay
244 needItemDelay = pl->GetSession()->GetAccountId() != rc_account;
247 if (money > 0 && GetSecurity() > SEC_PLAYER && sWorld.getConfig(CONFIG_GM_LOG_TRADE))
249 sLog.outCommand(GetAccountId(),"GM %s (Account: %u) mail money: %u to player: %s (Account: %u)",
250 GetPlayerName(), GetAccountId(), money, receiver.c_str(), rc_account);
254 // If theres is an item, there is a one hour delivery delay if sent to another account's character.
255 uint32 deliver_delay = needItemDelay ? sWorld.getConfig(CONFIG_MAIL_DELIVERY_DELAY) : 0;
257 // will delete item or place to receiver mail list
258 draft
259 .AddMoney(money)
260 .AddCOD(COD)
261 .SendMailTo(MailReceiver(receive, GUID_LOPART(rc)), pl, MAIL_CHECK_MASK_NONE, deliver_delay);
263 CharacterDatabase.BeginTransaction();
264 pl->SaveInventoryAndGoldToDB();
265 CharacterDatabase.CommitTransaction();
268 //called when mail is read
269 void WorldSession::HandleMailMarkAsRead(WorldPacket & recv_data )
271 uint64 mailbox;
272 uint32 mailId;
273 recv_data >> mailbox;
274 recv_data >> mailId;
276 if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox, GAMEOBJECT_TYPE_MAILBOX))
277 return;
279 Player *pl = _player;
280 Mail *m = pl->GetMail(mailId);
281 if (m)
283 if (pl->unReadMails)
284 --pl->unReadMails;
285 m->checked = m->checked | MAIL_CHECK_MASK_READ;
286 // m->expire_time = time(NULL) + (30 * DAY); // Expire time do not change at reading mail
287 pl->m_mailsUpdated = true;
288 m->state = MAIL_STATE_CHANGED;
292 //called when client deletes mail
293 void WorldSession::HandleMailDelete(WorldPacket & recv_data )
295 uint64 mailbox;
296 uint32 mailId;
297 recv_data >> mailbox;
298 recv_data >> mailId;
299 recv_data.read_skip<uint32>(); // mailTemplateId
301 if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox, GAMEOBJECT_TYPE_MAILBOX))
302 return;
304 Player* pl = _player;
305 pl->m_mailsUpdated = true;
306 Mail *m = pl->GetMail(mailId);
307 if(m)
309 // delete shouldn't show up for COD mails
310 if (m->COD)
312 pl->SendMailResult(mailId, MAIL_DELETED, MAIL_ERR_INTERNAL_ERROR);
313 return;
316 m->state = MAIL_STATE_DELETED;
318 pl->SendMailResult(mailId, MAIL_DELETED, MAIL_OK);
321 void WorldSession::HandleMailReturnToSender(WorldPacket & recv_data )
323 uint64 mailbox;
324 uint32 mailId;
325 recv_data >> mailbox;
326 recv_data >> mailId;
327 recv_data.read_skip<uint64>(); // original sender GUID for return to, not used
329 if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox, GAMEOBJECT_TYPE_MAILBOX))
330 return;
332 Player *pl = _player;
333 Mail *m = pl->GetMail(mailId);
334 if(!m || m->state == MAIL_STATE_DELETED || m->deliver_time > time(NULL))
336 pl->SendMailResult(mailId, MAIL_RETURNED_TO_SENDER, MAIL_ERR_INTERNAL_ERROR);
337 return;
339 //we can return mail now
340 //so firstly delete the old one
341 CharacterDatabase.BeginTransaction();
342 CharacterDatabase.PExecute("DELETE FROM mail WHERE id = '%u'", mailId);
343 // needed?
344 CharacterDatabase.PExecute("DELETE FROM mail_items WHERE mail_id = '%u'", mailId);
345 CharacterDatabase.CommitTransaction();
346 pl->RemoveMail(mailId);
348 // send back only to players and simple drop for other cases
349 if (m->messageType == MAIL_NORMAL)
351 MailDraft draft(m->subject, m->itemTextId);
352 if (m->mailTemplateId)
353 draft = MailDraft(m->mailTemplateId,false); // items already included
355 if(m->HasItems())
357 for(std::vector<MailItemInfo>::iterator itr2 = m->items.begin(); itr2 != m->items.end(); ++itr2)
359 Item *item = pl->GetMItem(itr2->item_guid);
360 if(item)
361 draft.AddItem(item);
362 else
364 //WTF?
367 pl->RemoveMItem(itr2->item_guid);
371 draft.AddMoney(m->money).SendReturnToSender(GetAccountId(), m->receiver, m->sender);
374 delete m; // we can deallocate old mail
375 pl->SendMailResult(mailId, MAIL_RETURNED_TO_SENDER, MAIL_OK);
378 //called when player takes item attached in mail
379 void WorldSession::HandleMailTakeItem(WorldPacket & recv_data )
381 uint64 mailbox;
382 uint32 mailId;
383 uint32 itemId;
384 recv_data >> mailbox;
385 recv_data >> mailId;
386 recv_data >> itemId; // item guid low
388 if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox, GAMEOBJECT_TYPE_MAILBOX))
389 return;
391 Player* pl = _player;
393 Mail* m = pl->GetMail(mailId);
394 if(!m || m->state == MAIL_STATE_DELETED || m->deliver_time > time(NULL))
396 pl->SendMailResult(mailId, MAIL_ITEM_TAKEN, MAIL_ERR_INTERNAL_ERROR);
397 return;
400 // prevent cheating with skip client money check
401 if(pl->GetMoney() < m->COD)
403 pl->SendMailResult(mailId, MAIL_ITEM_TAKEN, MAIL_ERR_NOT_ENOUGH_MONEY);
404 return;
407 Item *it = pl->GetMItem(itemId);
409 ItemPosCountVec dest;
410 uint8 msg = _player->CanStoreItem( NULL_BAG, NULL_SLOT, dest, it, false );
411 if( msg == EQUIP_ERR_OK )
413 m->RemoveItem(itemId);
414 m->removedItems.push_back(itemId);
416 if (m->COD > 0) // if there is COD, take COD money from player and send them to sender by mail
418 uint64 sender_guid = MAKE_NEW_GUID(m->sender, 0, HIGHGUID_PLAYER);
419 Player *receive = sObjectMgr.GetPlayer(sender_guid);
421 uint32 sender_accId = 0;
423 if( GetSecurity() > SEC_PLAYER && sWorld.getConfig(CONFIG_GM_LOG_TRADE) )
425 std::string sender_name;
426 if(receive)
428 sender_accId = receive->GetSession()->GetAccountId();
429 sender_name = receive->GetName();
431 else
433 // can be calculated early
434 sender_accId = sObjectMgr.GetPlayerAccountIdByGUID(sender_guid);
436 if(!sObjectMgr.GetPlayerNameByGUID(sender_guid, sender_name))
437 sender_name = sObjectMgr.GetMangosStringForDBCLocale(LANG_UNKNOWN);
439 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)",
440 GetPlayerName(), GetAccountId(), it->GetProto()->Name1, it->GetEntry(), it->GetCount(), m->COD, sender_name.c_str(), sender_accId);
442 else if(!receive)
443 sender_accId = sObjectMgr.GetPlayerAccountIdByGUID(sender_guid);
445 // check player existence
446 if(receive || sender_accId)
448 MailDraft(m->subject)
449 .AddMoney(m->COD)
450 .SendMailTo(MailReceiver(receive,m->sender),MailSender(MAIL_NORMAL,m->receiver), MAIL_CHECK_MASK_COD_PAYMENT);
453 pl->ModifyMoney( -int32(m->COD) );
455 m->COD = 0;
456 m->state = MAIL_STATE_CHANGED;
457 pl->m_mailsUpdated = true;
458 pl->RemoveMItem(it->GetGUIDLow());
460 uint32 count = it->GetCount(); // save counts before store and possible merge with deleting
461 pl->MoveItemToInventory(dest, it, true);
463 CharacterDatabase.BeginTransaction();
464 pl->SaveInventoryAndGoldToDB();
465 pl->_SaveMail();
466 CharacterDatabase.CommitTransaction();
468 pl->SendMailResult(mailId, MAIL_ITEM_TAKEN, MAIL_OK, 0, itemId, count);
470 else
471 pl->SendMailResult(mailId, MAIL_ITEM_TAKEN, MAIL_ERR_EQUIP_ERROR, msg);
474 void WorldSession::HandleMailTakeMoney(WorldPacket & recv_data )
476 uint64 mailbox;
477 uint32 mailId;
478 recv_data >> mailbox;
479 recv_data >> mailId;
481 if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox, GAMEOBJECT_TYPE_MAILBOX))
482 return;
484 Player *pl = _player;
486 Mail* m = pl->GetMail(mailId);
487 if(!m || m->state == MAIL_STATE_DELETED || m->deliver_time > time(NULL))
489 pl->SendMailResult(mailId, MAIL_MONEY_TAKEN, MAIL_ERR_INTERNAL_ERROR);
490 return;
493 pl->SendMailResult(mailId, MAIL_MONEY_TAKEN, MAIL_OK);
495 pl->ModifyMoney(m->money);
496 m->money = 0;
497 m->state = MAIL_STATE_CHANGED;
498 pl->m_mailsUpdated = true;
500 // save money and mail to prevent cheating
501 CharacterDatabase.BeginTransaction();
502 pl->SaveGoldToDB();
503 pl->_SaveMail();
504 CharacterDatabase.CommitTransaction();
507 //called when player lists his received mails
508 void WorldSession::HandleGetMailList(WorldPacket & recv_data )
510 uint64 mailbox;
511 recv_data >> mailbox;
513 if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox, GAMEOBJECT_TYPE_MAILBOX))
514 return;
516 // client can't work with packets > max int16 value
517 const uint32 maxPacketSize = 32767;
519 uint32 mailsCount = 0; // send to client mails amount
520 uint32 realCount = 0; // real mails amount
522 WorldPacket data(SMSG_MAIL_LIST_RESULT, 200); // guess size
523 data << uint32(0); // real mail's count
524 data << uint8(0); // mail's count
525 time_t cur_time = time(NULL);
527 for(PlayerMails::iterator itr = _player->GetMailBegin(); itr != _player->GetMailEnd(); ++itr)
529 // packet send mail count as uint8, prevent overflow
530 if(mailsCount >= 254)
532 realCount += 1;
533 continue;
536 // skip deleted or not delivered (deliver delay not expired) mails
537 if ((*itr)->state == MAIL_STATE_DELETED || cur_time < (*itr)->deliver_time)
538 continue;
540 uint8 item_count = (*itr)->items.size(); // max count is MAX_MAIL_ITEMS (12)
542 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);
544 if(data.wpos()+next_mail_size > maxPacketSize)
546 realCount += 1;
547 continue;
550 uint32 show_flags = 0;
551 if ((*itr)->messageType != MAIL_NORMAL)
552 show_flags |= MAIL_SHOW_DELETE;
553 if ((*itr)->messageType == MAIL_AUCTION)
554 show_flags |= MAIL_SHOW_AUCTION;
555 if ((*itr)->HasItems() && (*itr)->messageType == MAIL_NORMAL)
556 show_flags |= MAIL_SHOW_RETURN;
558 data << uint16(next_mail_size); // Message size
559 data << uint32((*itr)->messageID); // Message ID
560 data << uint8((*itr)->messageType); // Message Type
562 switch((*itr)->messageType)
564 case MAIL_NORMAL: // sender guid
565 data << uint64(MAKE_NEW_GUID((*itr)->sender, 0, HIGHGUID_PLAYER));
566 break;
567 case MAIL_CREATURE:
568 case MAIL_GAMEOBJECT:
569 case MAIL_AUCTION:
570 data << uint32((*itr)->sender); // creature/gameobject entry, auction id
571 break;
572 case MAIL_ITEM: // item entry (?) sender = "Unknown", NYI
573 data << uint32(0); // item entry
574 break;
577 data << uint32((*itr)->COD); // COD
578 data << uint32((*itr)->itemTextId); // sure about this
579 data << uint32(0); // unknown
580 data << uint32((*itr)->stationery); // stationery (Stationery.dbc)
581 data << uint32((*itr)->money); // Gold
582 data << uint32(show_flags); // unknown, 0x4 - auction, 0x10 - normal
583 data << float(((*itr)->expire_time-time(NULL))/DAY);// Time
584 data << uint32((*itr)->mailTemplateId); // mail template (MailTemplate.dbc)
585 data << (*itr)->subject; // Subject string - once 00, when mail type = 3
587 data << uint8(item_count); // client limit is 0x10
589 for(uint8 i = 0; i < item_count; ++i)
591 Item *item = _player->GetMItem((*itr)->items[i].item_guid);
592 // item index (0-6?)
593 data << uint8(i);
594 // item guid low?
595 data << uint32(item ? item->GetGUIDLow() : 0);
596 // entry
597 data << uint32(item ? item->GetEntry() : 0);
598 for(uint8 j = 0; j < MAX_INSPECTED_ENCHANTMENT_SLOT; ++j)
600 // unsure
601 data << uint32(item ? item->GetEnchantmentCharges((EnchantmentSlot)j) : 0);
602 // unsure
603 data << uint32(item ? item->GetEnchantmentDuration((EnchantmentSlot)j) : 0);
604 // unsure
605 data << uint32(item ? item->GetEnchantmentId((EnchantmentSlot)j) : 0);
607 // can be negative
608 data << uint32(item ? item->GetItemRandomPropertyId() : 0);
609 // unk
610 data << uint32(item ? item->GetItemSuffixFactor() : 0);
611 // stack count
612 data << uint32(item ? item->GetCount() : 0);
613 // charges
614 data << uint32(item ? item->GetSpellCharges() : 0);
615 // durability
616 data << uint32(item ? item->GetUInt32Value(ITEM_FIELD_MAXDURABILITY) : 0);
617 // durability
618 data << uint32(item ? item->GetUInt32Value(ITEM_FIELD_DURABILITY) : 0);
619 // unknown wotlk
620 data << uint8(0);
623 mailsCount += 1;
624 realCount += 1;
627 data.put<uint32>(0, realCount); // this will display warning about undelivered mail to player if realCount > mailsCount
628 data.put<uint8>(4, mailsCount); // set real send mails to client
629 SendPacket(&data);
631 // recalculate m_nextMailDelivereTime and unReadMails
632 _player->UpdateNextMailTimeAndUnreads();
635 ///this function is called when client needs mail message body, or when player clicks on item which has ITEM_FIELD_ITEM_TEXT_ID > 0
636 void WorldSession::HandleItemTextQuery(WorldPacket & recv_data )
638 uint32 itemTextId;
639 uint32 mailId; // this value can be item id in bag, but it is also mail id
640 uint32 unk; // maybe something like state - 0x70000000
642 recv_data >> itemTextId >> mailId >> unk;
644 //some check needed, if player has item with guid mailId, or has mail with id mailId
646 sLog.outDebug("CMSG_ITEM_TEXT_QUERY itemguid: %u, mailId: %u, unk: %u", itemTextId, mailId, unk);
648 WorldPacket data(SMSG_ITEM_TEXT_QUERY_RESPONSE, (4+10));// guess size
649 data << itemTextId;
650 data << sObjectMgr.GetItemText( itemTextId );
651 SendPacket(&data);
654 //used when player copies mail body to his inventory
655 void WorldSession::HandleMailCreateTextItem(WorldPacket & recv_data )
657 uint64 mailbox;
658 uint32 mailId;
660 recv_data >> mailbox;
661 recv_data >> mailId;
662 recv_data.read_skip<uint32>(); // mailTemplateId, non need, Mail store own 100% correct value anyway
664 if (!GetPlayer()->GetGameObjectIfCanInteractWith(mailbox, GAMEOBJECT_TYPE_MAILBOX))
665 return;
667 Player *pl = _player;
669 Mail* m = pl->GetMail(mailId);
670 if (!m || (!m->itemTextId && !m->mailTemplateId) || m->state == MAIL_STATE_DELETED || m->deliver_time > time(NULL))
672 pl->SendMailResult(mailId, MAIL_MADE_PERMANENT, MAIL_ERR_INTERNAL_ERROR);
673 return;
676 uint32 itemTextId = m->itemTextId;
678 // in mail template case we need create new text id
679 if(!itemTextId)
681 MailTemplateEntry const* mailTemplateEntry = sMailTemplateStore.LookupEntry(m->mailTemplateId);
682 if(!mailTemplateEntry)
684 pl->SendMailResult(mailId, MAIL_MADE_PERMANENT, MAIL_ERR_INTERNAL_ERROR);
685 return;
688 itemTextId = sObjectMgr.CreateItemText(mailTemplateEntry->content[GetSessionDbcLocale()]);
691 Item *bodyItem = new Item; // This is not bag and then can be used new Item.
692 if(!bodyItem->Create(sObjectMgr.GenerateLowGuid(HIGHGUID_ITEM), MAIL_BODY_ITEM_TEMPLATE, pl))
694 delete bodyItem;
695 return;
698 bodyItem->SetUInt32Value( ITEM_FIELD_ITEM_TEXT_ID, itemTextId );
699 bodyItem->SetUInt32Value( ITEM_FIELD_CREATOR, m->sender);
701 sLog.outDetail("HandleMailCreateTextItem mailid=%u",mailId);
703 ItemPosCountVec dest;
704 uint8 msg = _player->CanStoreItem( NULL_BAG, NULL_SLOT, dest, bodyItem, false );
705 if( msg == EQUIP_ERR_OK )
707 m->itemTextId = 0;
708 m->state = MAIL_STATE_CHANGED;
709 pl->m_mailsUpdated = true;
711 pl->StoreItem(dest, bodyItem, true);
712 //bodyItem->SetState(ITEM_NEW, pl); is set automatically
713 pl->SendMailResult(mailId, MAIL_MADE_PERMANENT, MAIL_OK);
715 else
717 pl->SendMailResult(mailId, MAIL_MADE_PERMANENT, MAIL_ERR_EQUIP_ERROR, msg);
718 delete bodyItem;
722 //TODO Fix me! ... this void has probably bad condition, but good data are sent
723 void WorldSession::HandleQueryNextMailTime(WorldPacket & /*recv_data*/ )
725 WorldPacket data(MSG_QUERY_NEXT_MAIL_TIME, 8);
727 if( _player->unReadMails > 0 )
729 data << uint32(0); // float
730 data << uint32(0); // count
732 uint32 count = 0;
733 time_t now = time(NULL);
734 for(PlayerMails::iterator itr = _player->GetMailBegin(); itr != _player->GetMailEnd(); ++itr)
736 Mail *m = (*itr);
737 // must be not checked yet
738 if(m->checked & MAIL_CHECK_MASK_READ)
739 continue;
741 // and already delivered
742 if(now < m->deliver_time)
743 continue;
745 data << uint64(m->sender); // sender guid
747 switch(m->messageType)
749 case MAIL_AUCTION:
750 data << uint32(2);
751 data << uint32(2);
752 data << uint32(m->stationery);
753 break;
754 default:
755 data << uint32(0);
756 data << uint32(0);
757 data << uint32(m->stationery);
758 break;
761 data << uint32(0xC6000000); // float unk, time or something
763 ++count;
764 if(count == 2) // do not display more than 2 mails
765 break;
767 data.put<uint32>(4, count);
769 else
771 data << uint32(0xC7A8C000);
772 data << uint32(0x00000000);
774 SendPacket(&data);
777 MailSender::MailSender( Object* sender, MailStationery stationery ) : m_stationery(stationery)
779 switch(sender->GetTypeId())
781 case TYPEID_UNIT:
782 m_messageType = MAIL_CREATURE;
783 m_senderId = sender->GetEntry();
784 break;
785 case TYPEID_GAMEOBJECT:
786 m_messageType = MAIL_GAMEOBJECT;
787 m_senderId = sender->GetEntry();
788 break;
789 case TYPEID_ITEM:
790 m_messageType = MAIL_ITEM;
791 m_senderId = sender->GetEntry();
792 break;
793 case TYPEID_PLAYER:
794 m_messageType = MAIL_NORMAL;
795 m_senderId = sender->GetGUIDLow();
796 break;
797 default:
798 m_messageType = MAIL_NORMAL;
799 m_senderId = 0; // will show mail from not existed player
800 sLog.outError( "MailSender::MailSender - Mail have unexpected sender typeid (%u)", sender->GetTypeId());
801 break;
805 MailSender::MailSender( AuctionEntry* sender )
806 : m_messageType(MAIL_AUCTION), m_senderId(sender->GetHouseId()), m_stationery(MAIL_STATIONERY_AUCTION)
811 MailReceiver::MailReceiver( Player* receiver ) : m_receiver(receiver), m_receiver_lowguid(receiver->GetGUIDLow())
815 MailReceiver::MailReceiver( Player* receiver,uint32 receiver_lowguid ) : m_receiver(receiver), m_receiver_lowguid(receiver_lowguid)
817 ASSERT(!receiver || receiver->GetGUIDLow() == receiver_lowguid);
820 MailDraft& MailDraft::AddItem( Item* item )
822 m_items[item->GetGUIDLow()] = item; return *this;
825 void MailDraft::prepareItems(Player* receiver)
827 if (!m_mailTemplateId || !m_mailTemplateItemsNeed)
828 return;
830 m_mailTemplateItemsNeed = false;
832 Loot mailLoot;
834 // can be empty
835 mailLoot.FillLoot(m_mailTemplateId, LootTemplates_Mail, receiver, true, true);
837 uint32 max_slot = mailLoot.GetMaxSlotInLootFor(receiver);
838 for(uint32 i = 0; m_items.size() < MAX_MAIL_ITEMS && i < max_slot; ++i)
840 if (LootItem* lootitem = mailLoot.LootItemInSlot(i,receiver))
842 if (Item* item = Item::CreateItem(lootitem->itemid,lootitem->count,receiver))
844 item->SaveToDB(); // save for prevent lost at next mail load, if send fail then item will deleted
845 AddItem(item);
851 void MailDraft::deleteIncludedItems( bool inDB /*= false*/ )
853 for(MailItemMap::iterator mailItemIter = m_items.begin(); mailItemIter != m_items.end(); ++mailItemIter)
855 Item* item = mailItemIter->second;
857 if(inDB)
858 CharacterDatabase.PExecute("DELETE FROM item_instance WHERE guid='%u'", item->GetGUIDLow());
860 delete item;
863 m_items.clear();
866 void MailDraft::SendReturnToSender(uint32 sender_acc, uint32 sender_guid, uint32 receiver_guid )
868 Player *receiver = sObjectMgr.GetPlayer(MAKE_NEW_GUID(receiver_guid, 0, HIGHGUID_PLAYER));
870 uint32 rc_account = 0;
871 if(!receiver)
872 rc_account = sObjectMgr.GetPlayerAccountIdByGUID(MAKE_NEW_GUID(receiver_guid, 0, HIGHGUID_PLAYER));
874 if(!receiver && !rc_account) // sender not exist
876 deleteIncludedItems(true);
877 return;
880 // prepare mail and send in other case
881 bool needItemDelay = false;
883 if(!m_items.empty())
885 // if item send to character at another account, then apply item delivery delay
886 needItemDelay = sender_acc != rc_account;
888 // set owner to new receiver (to prevent delete item with sender char deleting)
889 CharacterDatabase.BeginTransaction();
890 for(MailItemMap::iterator mailItemIter = m_items.begin(); mailItemIter != m_items.end(); ++mailItemIter)
892 Item* item = mailItemIter->second;
893 item->SaveToDB(); // item not in inventory and can be save standalone
894 // owner in data will set at mail receive and item extracting
895 CharacterDatabase.PExecute("UPDATE item_instance SET owner_guid = '%u' WHERE guid='%u'", receiver_guid, item->GetGUIDLow());
897 CharacterDatabase.CommitTransaction();
900 // If theres is an item, there is a one hour delivery delay.
901 uint32 deliver_delay = needItemDelay ? sWorld.getConfig(CONFIG_MAIL_DELIVERY_DELAY) : 0;
903 // will delete item or place to receiver mail list
904 SendMailTo(MailReceiver(receiver,receiver_guid), MailSender(MAIL_NORMAL, sender_guid), MAIL_CHECK_MASK_RETURNED, deliver_delay);
907 void MailDraft::SendMailTo(MailReceiver const& receiver, MailSender const& sender, MailCheckMask checked, uint32 deliver_delay)
909 Player* pReceiver = receiver.GetPlayer(); // can be NULL
911 if (pReceiver)
912 prepareItems(pReceiver); // generate mail template items
915 uint32 mailId = sObjectMgr.GenerateMailID();
917 time_t deliver_time = time(NULL) + deliver_delay;
919 uint32 expire_delay;
920 // auction mail without any items and money (auction sale note) pending 1 hour
921 if (sender.GetMailMessageType() == MAIL_AUCTION && m_items.empty() && !m_money)
922 expire_delay = HOUR;
923 // mail from battlemaster (rewardmarks) should last only one day
924 else if (sender.GetMailMessageType() == MAIL_CREATURE && sBattleGroundMgr.GetBattleMasterBG(sender.GetSenderId()) != BATTLEGROUND_TYPE_NONE)
925 expire_delay = DAY;
926 // default case: expire time if COD 3 days, if no COD 30 days
927 else
928 expire_delay = (m_COD > 0) ? 3 * DAY : 30 * DAY;
930 time_t expire_time = deliver_time + expire_delay;
932 // Add to DB
933 std::string safe_subject = GetSubject();
935 CharacterDatabase.BeginTransaction();
936 CharacterDatabase.escape_string(safe_subject);
937 CharacterDatabase.PExecute("INSERT INTO mail (id,messageType,stationery,mailTemplateId,sender,receiver,subject,itemTextId,has_items,expire_time,deliver_time,money,cod,checked) "
938 "VALUES ('%u', '%u', '%u', '%u', '%u', '%u', '%s', '%u', '%u', '" UI64FMTD "','" UI64FMTD "', '%u', '%u', '%d')",
939 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);
941 for(MailItemMap::const_iterator mailItemIter = m_items.begin(); mailItemIter != m_items.end(); ++mailItemIter)
943 Item* item = mailItemIter->second;
944 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());
946 CharacterDatabase.CommitTransaction();
948 // For online receiver update in game mail status and data
949 if (pReceiver)
951 pReceiver->AddNewMailDeliverTime(deliver_time);
953 Mail *m = new Mail;
954 m->messageID = mailId;
955 m->mailTemplateId = GetMailTemplateId();
956 m->subject = GetSubject();
957 m->itemTextId = GetBodyId();
958 m->money = GetMoney();
959 m->COD = GetCOD();
961 for(MailItemMap::const_iterator mailItemIter = m_items.begin(); mailItemIter != m_items.end(); ++mailItemIter)
963 Item* item = mailItemIter->second;
964 m->AddItem(item->GetGUIDLow(), item->GetEntry());
967 m->messageType = sender.GetMailMessageType();
968 m->stationery = sender.GetStationery();
969 m->sender = sender.GetSenderId();
970 m->receiver = receiver.GetPlayerGUIDLow();
971 m->expire_time = expire_time;
972 m->deliver_time = deliver_time;
973 m->checked = checked;
974 m->state = MAIL_STATE_UNCHANGED;
976 pReceiver->AddMail(m); // to insert new mail to beginning of maillist
978 if (!m_items.empty())
980 for(MailItemMap::iterator mailItemIter = m_items.begin(); mailItemIter != m_items.end(); ++mailItemIter)
981 pReceiver->AddMItem(mailItemIter->second);
984 else if (!m_items.empty())
985 deleteIncludedItems();