From f96b027ffd3df4433b47e6eb422689ea5f938efc Mon Sep 17 00:00:00 2001 From: Skjalf <47818697+Nyeriah@users.noreply.github.com> Date: Sun, 10 Oct 2021 12:15:24 -0300 Subject: [PATCH] fix(Core/Mail): load mails on login instead of when requested (#8065) --- .../Implementation/CharacterDatabase.cpp | 6 +- .../Implementation/CharacterDatabase.h | 2 - src/server/game/Entities/Player/Player.cpp | 68 +++-- src/server/game/Entities/Player/Player.h | 20 +- .../game/Entities/Player/PlayerStorage.cpp | 257 +++++++----------- src/server/game/Handlers/CharacterHandler.cpp | 13 +- src/server/game/Handlers/MailHandler.cpp | 27 +- 7 files changed, 157 insertions(+), 236 deletions(-) diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp index 64ea0bb0f..801de1a6b 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.cpp +++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp @@ -90,10 +90,8 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_SEL_CHARACTER_INVENTORY, "SELECT creatorGuid, giftCreatorGuid, count, duration, charges, flags, enchantments, randomPropertyId, durability, playedTime, text, bag, slot, " "item, itemEntry FROM character_inventory ci JOIN item_instance ii ON ci.item = ii.guid WHERE ci.guid = ? ORDER BY bag, slot", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_CHARACTER_ACTIONS, "SELECT a.button, a.action, a.type FROM character_action as a, characters as c WHERE a.guid = c.guid AND a.spec = c.activeTalentGroup AND a.guid = ? ORDER BY button", CONNECTION_ASYNC); - PrepareStatement(CHAR_SEL_CHARACTER_MAILCOUNT, "SELECT COUNT(id) FROM mail WHERE receiver = ? AND deliver_time <= ?", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_CHARACTER_MAILCOUNT_UNREAD, "SELECT COUNT(id) FROM mail WHERE receiver = ? AND (checked & 1) = 0 AND deliver_time <= ?", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_CHARACTER_MAILCOUNT_UNREAD_SYNCH, "SELECT COUNT(id) FROM mail WHERE receiver = ? AND (checked & 1) = 0 AND deliver_time <= ?", CONNECTION_SYNCH); - PrepareStatement(CHAR_SEL_CHARACTER_MAILDATE, "SELECT MIN(deliver_time) FROM mail WHERE receiver = ? AND (checked & 1) = 0", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_CHARACTER_SOCIALLIST, "SELECT friend, flags, note FROM character_social JOIN characters ON characters.guid = character_social.friend WHERE character_social.guid = ? AND deleteinfos_name IS NULL LIMIT 255", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_CHARACTER_HOMEBIND, "SELECT mapId, zoneId, posX, posY, posZ FROM character_homebind WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_CHARACTER_SPELLCOOLDOWNS, "SELECT spell, category, item, time, needSend FROM character_spell_cooldown WHERE guid = ?", CONNECTION_ASYNC); @@ -115,7 +113,7 @@ void CharacterDatabaseConnection::DoPrepareStatements() // End LoginQueryHolder content PrepareStatement(CHAR_SEL_CHARACTER_ACTIONS_SPEC, "SELECT button, action, type FROM character_action WHERE guid = ? AND spec = ? ORDER BY button", CONNECTION_ASYNC); - PrepareStatement(CHAR_SEL_MAILITEMS, "SELECT creatorGuid, giftCreatorGuid, count, duration, charges, flags, enchantments, randomPropertyId, durability, playedTime, text, item_guid, itemEntry, owner_guid FROM mail_items mi JOIN item_instance ii ON mi.item_guid = ii.guid WHERE mail_id = ?", CONNECTION_SYNCH); + PrepareStatement(CHAR_SEL_MAILITEMS, "SELECT creatorGuid, giftCreatorGuid, count, duration, charges, flags, enchantments, randomPropertyId, durability, playedTime, text, item_guid, itemEntry, ii.owner_guid, m.id FROM mail_items mi INNER JOIN mail m ON mi.mail_id = m.id LEFT JOIN item_instance ii ON mi.item_guid = ii.guid WHERE m.receiver = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_AUCTION_ITEMS, "SELECT creatorGuid, giftCreatorGuid, count, duration, charges, flags, enchantments, randomPropertyId, durability, playedTime, text, itemguid, itemEntry FROM auctionhouse ah JOIN item_instance ii ON ah.itemguid = ii.guid", CONNECTION_SYNCH); PrepareStatement(CHAR_SEL_AUCTIONS, "SELECT id, houseid, itemguid, itemEntry, count, itemowner, buyoutprice, time, buyguid, lastbid, startbid, deposit FROM auctionhouse ah INNER JOIN item_instance ii ON ii.guid = ah.itemguid", CONNECTION_SYNCH); PrepareStatement(CHAR_INS_AUCTION, "INSERT INTO auctionhouse (id, houseid, itemguid, itemowner, buyoutprice, time, buyguid, lastbid, startbid, deposit) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); @@ -415,7 +413,7 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_SEL_CHAR_SOCIAL, "SELECT DISTINCT guid FROM character_social WHERE friend = ?", CONNECTION_SYNCH); PrepareStatement(CHAR_SEL_CHAR_OLD_CHARS, "SELECT guid, deleteInfos_Account FROM characters WHERE deleteDate IS NOT NULL AND deleteDate < ?", CONNECTION_SYNCH); PrepareStatement(CHAR_SEL_ARENA_TEAM_ID_BY_PLAYER_GUID, "SELECT arena_team_member.arenateamid FROM arena_team_member JOIN arena_team ON arena_team_member.arenateamid = arena_team.arenateamid WHERE guid = ? AND type = ? LIMIT 1", CONNECTION_SYNCH); - PrepareStatement(CHAR_SEL_MAIL, "SELECT id, messageType, sender, receiver, subject, body, has_items, expire_time, deliver_time, money, cod, checked, stationery, mailTemplateId, auctionId FROM mail WHERE receiver = ? AND deliver_time <= ? ORDER BY id DESC LIMIT 50", CONNECTION_SYNCH); + PrepareStatement(CHAR_SEL_MAIL, "SELECT id, messageType, sender, receiver, subject, body, expire_time, deliver_time, money, cod, checked, stationery, mailTemplateId, auctionId FROM mail WHERE receiver = ? AND deliver_time <= ? ORDER BY id DESC LIMIT 50", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_NEXT_MAIL_DELIVERYTIME, "SELECT MIN(deliver_time) FROM mail WHERE receiver = ? AND deliver_time > ? AND (checked & 1) = 0 LIMIT 1", CONNECTION_SYNCH); PrepareStatement(CHAR_DEL_CHAR_AURA_FROZEN, "DELETE FROM character_aura WHERE spell = 9454 AND guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_SEL_CHAR_INVENTORY_COUNT_ITEM, "SELECT COUNT(itemEntry) FROM character_inventory ci INNER JOIN item_instance ii ON ii.guid = ci.item WHERE itemEntry = ?", CONNECTION_SYNCH); diff --git a/src/server/database/Database/Implementation/CharacterDatabase.h b/src/server/database/Database/Implementation/CharacterDatabase.h index 507fa08e8..7b5a17ed9 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.h +++ b/src/server/database/Database/Implementation/CharacterDatabase.h @@ -79,10 +79,8 @@ enum CharacterDatabaseStatements : uint32 CHAR_SEL_CHARACTER_INVENTORY, CHAR_SEL_CHARACTER_ACTIONS, CHAR_SEL_CHARACTER_ACTIONS_SPEC, - CHAR_SEL_CHARACTER_MAILCOUNT, CHAR_SEL_CHARACTER_MAILCOUNT_UNREAD, CHAR_SEL_CHARACTER_MAILCOUNT_UNREAD_SYNCH, - CHAR_SEL_CHARACTER_MAILDATE, CHAR_SEL_CHARACTER_SOCIALLIST, CHAR_SEL_CHARACTER_HOMEBIND, CHAR_SEL_CHARACTER_SPELLCOOLDOWNS, diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 29994d518..c6adc6cc9 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -436,7 +436,7 @@ Player::~Player() delete itr->second; //all mailed items should be deleted, also all mail should be deallocated - for (PlayerMails::iterator itr = m_mailCache.begin(); itr != m_mailCache.end(); ++itr) + for (PlayerMails::iterator itr = m_mail.begin(); itr != m_mail.end(); ++itr) { delete *itr; } @@ -2747,12 +2747,12 @@ void Player::SendInitialSpells() void Player::RemoveMail(uint32 id) { - for (PlayerMails::iterator itr = m_mailCache.begin(); itr != m_mailCache.end(); ++itr) + for (PlayerMails::iterator itr = m_mail.begin(); itr != m_mail.end(); ++itr) { if ((*itr)->messageID == id) { //do not delete item, because Player::removeMail() is called when returning mail to sender. - m_mailCache.erase(itr); + m_mail.erase(itr); return; } } @@ -2784,7 +2784,8 @@ void Player::SendNewMail() void Player::AddNewMailDeliverTime(time_t deliver_time) { - sWorld->UpdateGlobalPlayerMails(GetGUID().GetCounter(), totalMailCount, false); + sWorld->UpdateGlobalPlayerMails(GetGUID().GetCounter(), GetMailSize(), false); + if (deliver_time <= time(nullptr)) // ready now { ++unReadMails; @@ -3693,7 +3694,7 @@ void Player::SetFreeTalentPoints(uint32 points) Mail* Player::GetMail(uint32 id) { - for (PlayerMails::iterator itr = m_mailCache.begin(); itr != m_mailCache.end(); ++itr) + for (PlayerMails::iterator itr = m_mail.begin(); itr != m_mail.end(); ++itr) { if ((*itr)->messageID == id) { @@ -3912,6 +3913,25 @@ void Player::DeleteFromDB(ObjectGuid::LowType lowGuid, uint32 accountId, bool up if (resultMail) { + std::unordered_map> itemsByMail; + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_MAILITEMS); + stmt->setUInt32(0, lowGuid); + PreparedQueryResult resultItems = CharacterDatabase.Query(stmt); + + if (resultItems) + { + do + { + Field* fields = resultItems->Fetch(); + uint32 mailId = fields[14].GetUInt32(); + if (Item* mailItem = _LoadMailedItem(playerGuid, nullptr, mailId, nullptr, fields)) + { + itemsByMail[mailId].push_back(mailItem); + } + } while (resultItems->NextRow()); + } + do { Field* mailFields = resultMail->Fetch(); @@ -3947,40 +3967,16 @@ void Player::DeleteFromDB(ObjectGuid::LowType lowGuid, uint32 accountId, bool up if (mailTemplateId) draft = MailDraft(mailTemplateId, false); // items are already included - if (has_items) + auto itemsItr = itemsByMail.find(mail_id); + if (itemsItr != itemsByMail.end()) { - // Data needs to be at first place for Item::LoadFromDB - stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_MAILITEMS); - stmt->setUInt32(0, mail_id); - PreparedQueryResult resultItems = CharacterDatabase.Query(stmt); - if (resultItems) + for (Item* item : itemsItr->second) { - do - { - Field* itemFields = resultItems->Fetch(); - ObjectGuid::LowType item_guidlow = itemFields[11].GetUInt32(); - uint32 item_template = itemFields[12].GetUInt32(); - - ItemTemplate const* itemProto = sObjectMgr->GetItemTemplate(item_template); - if (!itemProto) - { - stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE); - stmt->setUInt32(0, item_guidlow); - trans->Append(stmt); - continue; - } - - Item* pItem = NewItemOrBag(itemProto); - if (!pItem->LoadFromDB(item_guidlow, playerGuid, itemFields, item_template)) - { - pItem->FSetState(ITEM_REMOVED); - pItem->SaveToDB(trans); // it also deletes item object! - continue; - } - - draft.AddItem(pItem); - } while (resultItems->NextRow()); + draft.AddItem(item); } + + // MailDraft will take care of freeing memory. + itemsByMail.erase(itemsItr); } stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_ITEM_BY_ID); diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 81d964d1c..87d0a8359 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -842,9 +842,8 @@ enum PlayerLoginQueryIndex PLAYER_LOGIN_QUERY_LOAD_REPUTATION = 7, PLAYER_LOGIN_QUERY_LOAD_INVENTORY = 8, PLAYER_LOGIN_QUERY_LOAD_ACTIONS = 9, - PLAYER_LOGIN_QUERY_LOAD_MAIL_COUNT = 10, - PLAYER_LOGIN_QUERY_LOAD_MAIL_UNREAD_COUNT = 11, - PLAYER_LOGIN_QUERY_LOAD_MAIL_DATE = 12, + PLAYER_LOGIN_QUERY_LOAD_MAILS = 10, + PLAYER_LOGIN_QUERY_LOAD_MAIL_ITEMS = 11, PLAYER_LOGIN_QUERY_LOAD_SOCIAL_LIST = 13, PLAYER_LOGIN_QUERY_LOAD_HOME_BIND = 14, PLAYER_LOGIN_QUERY_LOAD_SPELL_COOLDOWNS = 15, @@ -1576,19 +1575,17 @@ public: void RemoveMail(uint32 id); - void AddMail(Mail* mail) { totalMailCount++; m_mailCache.push_front(mail); }// for call from WorldSession::SendMailTo - uint32 GetMailSize() { return totalMailCount; } - uint32 GetMailCacheSize() { return m_mailCache.size();} + void AddMail(Mail* mail) { m_mail.push_front(mail); }// for call from WorldSession::SendMailTo + uint32 GetMailSize() { return m_mail.size();} Mail* GetMail(uint32 id); - PlayerMails const& GetMails() const { return m_mailCache; } + PlayerMails const& GetMails() const { return m_mail; } /*********************************************************/ /*** MAILED ITEMS SYSTEM ***/ /*********************************************************/ uint8 unReadMails; - uint32 totalMailCount; time_t m_nextMailDelivereTime; typedef std::unordered_map ItemMap; @@ -2642,9 +2639,8 @@ public: void _LoadAuras(PreparedQueryResult result, uint32 timediff); void _LoadGlyphAuras(); void _LoadInventory(PreparedQueryResult result, uint32 timeDiff); - void _LoadMailInit(PreparedQueryResult resultMailCount, PreparedQueryResult resultUnread, PreparedQueryResult resultDelivery); - void _LoadMail(); - void _LoadMailedItems(Mail* mail); + void _LoadMail(PreparedQueryResult mailsResult, PreparedQueryResult mailItemsResult); + static Item* _LoadMailedItem(ObjectGuid const& playerGuid, Player* player, uint32 mailId, Mail* mail, Field* fields); void _LoadQuestStatus(PreparedQueryResult result); void _LoadQuestStatusRewarded(PreparedQueryResult result); void _LoadDailyQuestStatus(PreparedQueryResult result); @@ -2741,7 +2737,7 @@ public: uint32 m_GuildIdInvited; uint32 m_ArenaTeamIdInvited; - PlayerMails m_mailCache; + PlayerMails m_mail; PlayerSpellMap m_spells; PlayerTalentMap m_talents; uint32 m_lastPotionId; // last used health/mana potion in combat, that block next potion use diff --git a/src/server/game/Entities/Player/PlayerStorage.cpp b/src/server/game/Entities/Player/PlayerStorage.cpp index 15b767443..50674566c 100644 --- a/src/server/game/Entities/Player/PlayerStorage.cpp +++ b/src/server/game/Entities/Player/PlayerStorage.cpp @@ -5454,9 +5454,6 @@ bool Player::LoadFromDB(ObjectGuid playerGuid, CharacterDatabaseQueryHolder cons // apply original stats mods before spell loading or item equipment that call before equip _RemoveStatsMods() - //mails are loaded only when needed ;-) - when player in game click on mailbox. - //_LoadMail(); - m_specsCount = fields[64].GetUInt8(); m_activeSpec = fields[65].GetUInt8(); @@ -5495,8 +5492,7 @@ bool Player::LoadFromDB(ObjectGuid playerGuid, CharacterDatabaseQueryHolder cons m_reputationMgr->LoadFromDB(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_REPUTATION)); // xinef: load mails before inventory, so problematic items can be added to already loaded mails - // unread mails and next delivery time, actual mails not loaded - _LoadMailInit(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_MAIL_COUNT), holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_MAIL_UNREAD_COUNT), holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_MAIL_DATE)); + _LoadMail(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_MAILS), holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_MAIL_ITEMS)); _LoadInventory(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_INVENTORY), time_diff); @@ -6124,161 +6120,98 @@ Item* Player::_LoadItem(CharacterDatabaseTransaction trans, uint32 zoneId, uint3 } // load mailed item which should receive current player -void Player::_LoadMailedItems(Mail* mail) +Item* Player::_LoadMailedItem(ObjectGuid const& playerGuid, Player* player, uint32 mailId, Mail* mail, Field* fields) { - // data needs to be at first place for Item::LoadFromDB - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_MAILITEMS); - stmt->setUInt32(0, mail->messageID); - PreparedQueryResult result = CharacterDatabase.Query(stmt); - if (!result) - return; + ObjectGuid::LowType itemGuid = fields[11].GetUInt32(); + uint32 itemEntry = fields[12].GetUInt32(); - do + ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemEntry); + if (!proto) { - Field* fields = result->Fetch(); + LOG_ERROR("entities.player", "Player %s (%s) has unknown item in mailed items (GUID: %u, Entry: %u) in mail (%u), deleted.", + player ? player->GetName().c_str() : "", playerGuid.ToString().c_str(), itemGuid, itemEntry, mailId); - ObjectGuid::LowType itemGuid = fields[11].GetUInt32(); - uint32 itemTemplate = fields[12].GetUInt32(); + CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); - mail->AddItem(itemGuid, itemTemplate); + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_INVALID_MAIL_ITEM); + stmt->setUInt32(0, itemGuid); + trans->Append(stmt); - ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemTemplate); + CharacterDatabase.CommitTransaction(trans); + return nullptr; + } - if (!proto) - { - LOG_ERROR("entities.player", "Player %s has unknown item_template (ProtoType) in mailed items (GUID: %u, template: %u) in mail (%u), deleted.", - GetGUID().ToString().c_str(), itemGuid, itemTemplate, mail->messageID); + Item* item = NewItemOrBag(proto); - stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_INVALID_MAIL_ITEM); - stmt->setUInt32(0, itemGuid); - CharacterDatabase.Execute(stmt); + ObjectGuid ownerGuid = fields[13].GetUInt32() ? ObjectGuid::Create(fields[13].GetUInt32()) : ObjectGuid::Empty; + if (!item->LoadFromDB(itemGuid, ownerGuid, fields, itemEntry)) + { + LOG_ERROR("entities.player", "Player::_LoadMailedItems: Item (GUID: %u) in mail (%u) doesn't exist, deleted from mail.", itemGuid, mailId); - stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE); - stmt->setUInt32(0, itemGuid); - CharacterDatabase.Execute(stmt); - continue; - } + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_ITEM); + stmt->setUInt32(0, itemGuid); + CharacterDatabase.Execute(stmt); - Item* item = NewItemOrBag(proto); - if (!item->LoadFromDB(itemGuid, ObjectGuid::Create(fields[13].GetUInt32()), fields, itemTemplate)) - { - LOG_ERROR("entities.player", "Player::_LoadMailedItems - Item in mail (%u) doesn't exist !!!! - item guid: %u, deleted from mail", mail->messageID, itemGuid); + item->FSetState(ITEM_REMOVED); - stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_ITEM); - stmt->setUInt32(0, itemGuid); - CharacterDatabase.Execute(stmt); + CharacterDatabaseTransaction temp = CharacterDatabaseTransaction(nullptr); + item->SaveToDB(temp); + return nullptr; + } - item->FSetState(ITEM_REMOVED); + if (mail) + { + mail->AddItem(itemGuid, itemEntry); + } - CharacterDatabaseTransaction temp = CharacterDatabaseTransaction(nullptr); - item->SaveToDB(temp); // it also deletes item object ! - continue; - } + if (player) + { + player->AddMItem(item); + } - AddMItem(item); - } while (result->NextRow()); + return item; } -void Player::_LoadMailInit(PreparedQueryResult resultMailCount, PreparedQueryResult resultUnread, PreparedQueryResult resultDelivery) +void Player::_LoadMail(PreparedQueryResult mailsResult, PreparedQueryResult mailItemsResult) { - //Set count for all mails used to display correct size later one - if (resultMailCount) - { - totalMailCount = uint32((*resultMailCount)[0].GetUInt32()); - sWorld->UpdateGlobalPlayerMails(GetGUID().GetCounter(), totalMailCount, false); - } - - //set a count of unread mails - //QueryResult* resultMails = CharacterDatabase.PQuery("SELECT COUNT(id) FROM mail WHERE receiver = '%u' AND (checked & 1)=0 AND deliver_time <= '" UI64FMTD "'", playerGuid.GetCounter(), (uint64)cTime); - if (resultUnread) - unReadMails = uint8((*resultUnread)[0].GetUInt64()); - - // store nearest delivery time (it > 0 and if it < current then at next player update SendNewMaill will be called) - //resultMails = CharacterDatabase.PQuery("SELECT MIN(deliver_time) FROM mail WHERE receiver = '%u' AND (checked & 1)=0", playerGuid.GetCounter()); - if (resultDelivery) - m_nextMailDelivereTime = time_t((*resultDelivery)[0].GetUInt32()); -} - -void Player::_LoadMail() -{ - //In case we are not expecting to receive new mail we just exits, not really necessary to refresh any data - if (m_nextMailDelivereTime > time(nullptr)) - { - return; - } - - if (m_mailsUpdated) - { - //Save changed data to the sql before refreshing so we always get up to date data - CharacterDatabaseTransaction saveTransaction = CharacterDatabase.BeginTransaction(); - _SaveMail(saveTransaction); - CharacterDatabase.CommitTransaction(saveTransaction); - } - - //This should in theory always be < 100 - for (PlayerMails::iterator itr = m_mailCache.begin(); itr != m_mailCache.end();) - { - Mail* mail = *itr; - itr = m_mailCache.erase(itr); - - if (mail) - delete mail; - } - - // Delete mailed items aswell - // Created again below in Player::_LoadMailedItems - for (ItemMap::iterator iter = mMitems.begin(); iter != mMitems.end(); ++iter) - delete iter->second; - std::set pendingAuctions; std::unordered_map pendingAuctionMails; - - mMitems.clear(); - - //Now load the new ones - m_mailCache.clear(); - CharacterDatabaseTransaction pendingAuctionsTrans = CharacterDatabase.BeginTransaction(); - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_MAIL); - stmt->setUInt32(0, GetGUID().GetCounter()); - stmt->setUInt32(1, uint32(time(nullptr))); - PreparedQueryResult result = CharacterDatabase.Query(stmt); - if (result) + m_mail.clear(); + + std::unordered_map mailById; + + if (mailsResult) { do { - Field* fields = result->Fetch(); - + Field* fields = mailsResult->Fetch(); Mail* m = new Mail; - m->messageID = fields[0].GetUInt32(); - m->messageType = fields[1].GetUInt8(); - m->sender = fields[2].GetUInt32(); - m->receiver = fields[3].GetUInt32(); - m->subject = fields[4].GetString(); - m->body = fields[5].GetString(); - bool has_items = fields[6].GetBool(); - m->expire_time = time_t(fields[7].GetUInt32()); - m->deliver_time = time_t(fields[8].GetUInt32()); - m->money = fields[9].GetUInt32(); - m->COD = fields[10].GetUInt32(); - m->checked = fields[11].GetUInt8(); - m->stationery = fields[12].GetUInt8(); - m->mailTemplateId = fields[13].GetInt16(); - m->auctionId = fields[14].GetInt32(); + m->messageID = fields[0].GetUInt32(); + m->messageType = fields[1].GetUInt8(); + m->sender = fields[2].GetUInt32(); + m->receiver = fields[3].GetUInt32(); + m->subject = fields[4].GetString(); + m->body = fields[5].GetString(); + m->expire_time = time_t(fields[6].GetUInt32()); + m->deliver_time = time_t(fields[7].GetUInt32()); + m->money = fields[8].GetUInt32(); + m->COD = fields[9].GetUInt32(); + m->checked = fields[10].GetUInt8(); + m->stationery = fields[11].GetUInt8(); + m->mailTemplateId = fields[12].GetInt16(); + m->auctionId = fields[13].GetInt32(); if (m->mailTemplateId && !sMailTemplateStore.LookupEntry(m->mailTemplateId)) { - LOG_ERROR("entities.player", "Player::_LoadMail - Mail (%u) have not existed MailTemplateId (%u), remove at load", m->messageID, m->mailTemplateId); + LOG_ERROR("entities.player", "Player::_LoadMail: Mail (%u) has nonexistent MailTemplateId (%u), remove at load", m->messageID, m->mailTemplateId); m->mailTemplateId = 0; } m->state = MAIL_STATE_UNCHANGED; - if (has_items) - _LoadMailedItems(m); - // Do not load expired pending sale mail if there is already delivery auction mail if (m->auctionId < 0 && m->expire_time <= time(nullptr)) { @@ -6289,9 +6222,6 @@ void Player::_LoadMail() stmt2->setUInt32(0, m->messageID); pendingAuctionsTrans->Append(stmt2); - if (totalMailCount > 0) - --totalMailCount; - if (unReadMails > 0 && (m->checked & MAIL_CHECK_MASK_READ) == 0) --unReadMails; @@ -6302,37 +6232,48 @@ void Player::_LoadMail() pendingAuctionMails[auctionId] = m; } else if (m->auctionId > 0) + { pendingAuctions.insert(m->auctionId); + } - m_mailCache.push_back(m); - } - while (result->NextRow()); + for (auto itr : pendingAuctionMails) + { + uint32 auctionId = itr.first; + if (pendingAuctions.count(auctionId)) + { + Mail* mail = itr.second; + + CharacterDatabasePreparedStatement* stmt2 = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_BY_ID); + stmt2->setUInt32(0, mail->messageID); + pendingAuctionsTrans->Append(stmt2); + + if (unReadMails > 0 && (mail->checked & MAIL_CHECK_MASK_READ) == 0) + { + --unReadMails; + } + + m_mail.erase(std::remove(m_mail.begin(), m_mail.end(), mail)); + + delete mail; + } + } + + m_mail.push_back(m); + mailById[m->messageID] = m; + } while (mailsResult->NextRow()); } - for (auto itr : pendingAuctionMails) + if (mailItemsResult) { - uint32 auctionId = itr.first; - if (pendingAuctions.count(auctionId)) + do { - Mail* mail = itr.second; - - CharacterDatabasePreparedStatement* stmt2 = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_BY_ID); - stmt2->setUInt32(0, mail->messageID); - pendingAuctionsTrans->Append(stmt2); - - if (totalMailCount > 0) - --totalMailCount; - - if (unReadMails > 0 && (mail->checked & MAIL_CHECK_MASK_READ) == 0) - --unReadMails; - - m_mailCache.erase(std::remove(m_mailCache.begin(), m_mailCache.end(), mail)); - - delete mail; - } + Field* fields = mailItemsResult->Fetch(); + uint32 mailId = fields[14].GetUInt32(); + _LoadMailedItem(GetGUID(), this, mailId, mailById[mailId], fields); + } while (mailItemsResult->NextRow()); } - CharacterDatabase.CommitTransaction(pendingAuctionsTrans); + UpdateNextMailTimeAndUnreads(); } void Player::LoadPet() @@ -7490,14 +7431,14 @@ void Player::_SaveInventory(CharacterDatabaseTransaction trans) void Player::_SaveMail(CharacterDatabaseTransaction trans) { - if (!GetMailCacheSize() || !m_mailsUpdated) + if (!GetMailSize() || !m_mailsUpdated) { return; } CharacterDatabasePreparedStatement* stmt = nullptr; - for (PlayerMails::iterator itr = m_mailCache.begin(); itr != m_mailCache.end(); ++itr) + for (PlayerMails::iterator itr = m_mail.begin(); itr != m_mail.end(); ++itr) { Mail* m = (*itr); if (m->state == MAIL_STATE_CHANGED) @@ -7543,20 +7484,18 @@ void Player::_SaveMail(CharacterDatabaseTransaction trans) stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_ITEM_BY_ID); stmt->setUInt32(0, m->messageID); trans->Append(stmt); - if (totalMailCount > 0) - totalMailCount--; } } //deallocate deleted mails... - for (PlayerMails::iterator itr = m_mailCache.begin(); itr != m_mailCache.end();) + for (PlayerMails::iterator itr = m_mail.begin(); itr != m_mail.end();) { if ((*itr)->state == MAIL_STATE_DELETED) { Mail* m = *itr; - m_mailCache.erase(itr); + m_mail.erase(itr); delete m; - itr = m_mailCache.begin(); + itr = m_mail.begin(); } else ++itr; diff --git a/src/server/game/Handlers/CharacterHandler.cpp b/src/server/game/Handlers/CharacterHandler.cpp index 06b3b9413..2dd2fb415 100644 --- a/src/server/game/Handlers/CharacterHandler.cpp +++ b/src/server/game/Handlers/CharacterHandler.cpp @@ -122,19 +122,14 @@ bool LoginQueryHolder::Initialize() stmt->setUInt32(0, lowGuid); res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_ACTIONS, stmt); - stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_MAILCOUNT); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_MAIL); stmt->setUInt32(0, lowGuid); stmt->setUInt32(1, uint32(time(nullptr))); - res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_MAIL_COUNT, stmt); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_MAILS, stmt); - stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_MAILCOUNT_UNREAD); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_MAILITEMS); stmt->setUInt32(0, lowGuid); - stmt->setUInt32(1, uint32(time(nullptr))); - res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_MAIL_UNREAD_COUNT, stmt); - - stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_MAILDATE); - stmt->setUInt32(0, lowGuid); - res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_MAIL_DATE, stmt); + res &= SetPreparedQuery(PLAYER_LOGIN_QUERY_LOAD_MAIL_ITEMS, stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CHARACTER_SOCIALLIST); stmt->setUInt32(0, lowGuid); diff --git a/src/server/game/Handlers/MailHandler.cpp b/src/server/game/Handlers/MailHandler.cpp index 2699a2af7..35aac8a9e 100644 --- a/src/server/game/Handlers/MailHandler.cpp +++ b/src/server/game/Handlers/MailHandler.cpp @@ -120,11 +120,11 @@ void WorldSession::HandleSendMail(WorldPacket& recvData) return; } - ObjectGuid rc; + ObjectGuid receiverGuid; if (normalizePlayerName(receiver)) - rc = sObjectMgr->GetPlayerGUIDByName(receiver); + receiverGuid = sObjectMgr->GetPlayerGUIDByName(receiver); - if (!rc) + if (!receiverGuid) { LOG_DEBUG("network.opcode", "Player %s 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", player->GetGUID().ToString().c_str(), receiver.c_str(), subject.c_str(), body.c_str(), items_count, money, COD, unk1, unk2); @@ -133,9 +133,9 @@ void WorldSession::HandleSendMail(WorldPacket& recvData) } LOG_DEBUG("network.opcode", "Player %s is sending mail to %s (%s) with subject %s and body %s includes %u items, %u copper and %u COD copper with unk1 = %u, unk2 = %u", - player->GetGUID().ToString().c_str(), receiver.c_str(), rc.ToString().c_str(), subject.c_str(), body.c_str(), items_count, money, COD, unk1, unk2); + player->GetGUID().ToString().c_str(), receiver.c_str(), receiverGuid.ToString().c_str(), subject.c_str(), body.c_str(), items_count, money, COD, unk1, unk2); - if (player->GetGUID() == rc) + if (player->GetGUID() == receiverGuid) { player->SendMailResult(0, MAIL_SEND, MAIL_ERR_CANNOT_SEND_TO_SELF); return; @@ -165,7 +165,7 @@ void WorldSession::HandleSendMail(WorldPacket& recvData) return; } - Player* receive = ObjectAccessor::FindConnectedPlayer(rc); + Player* receive = ObjectAccessor::FindConnectedPlayer(receiverGuid); uint32 rc_teamId = TEAM_NEUTRAL; uint16 mails_count = 0; //do not allow to send to one player more than 100 mails @@ -178,7 +178,7 @@ void WorldSession::HandleSendMail(WorldPacket& recvData) else { // xinef: get data from global storage - if (GlobalPlayerData const* playerData = sWorld->GetGlobalPlayerData(rc.GetCounter())) + if (GlobalPlayerData const* playerData = sWorld->GetGlobalPlayerData(receiverGuid.GetCounter())) { rc_teamId = Player::TeamIdForRace(playerData->race); mails_count = playerData->mailCount; @@ -207,7 +207,7 @@ void WorldSession::HandleSendMail(WorldPacket& recvData) } }*/ - uint32 rc_account = receive ? receive->GetSession()->GetAccountId() : sObjectMgr->GetPlayerAccountIdByGUID(rc.GetCounter()); + uint32 rc_account = receive ? receive->GetSession()->GetAccountId() : sObjectMgr->GetPlayerAccountIdByGUID(receiverGuid.GetCounter()); if (/*!accountBound*/ GetAccountId() != rc_account && !sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_MAIL) && player->GetTeamId() != rc_teamId && AccountMgr::IsPlayerAccount(GetSecurity())) { @@ -264,8 +264,10 @@ void WorldSession::HandleSendMail(WorldPacket& recvData) return; } - if (!sScriptMgr->CanSendMail(player, rc, mailbox, subject, body, money, COD, item)) + if (!sScriptMgr->CanSendMail(player, receiverGuid, mailbox, subject, body, money, COD, item)) + { return; + } items[i] = item; } @@ -295,7 +297,7 @@ void WorldSession::HandleSendMail(WorldPacket& recvData) item->DeleteFromInventoryDB(trans); // deletes item from character's inventory if (item->GetState() == ITEM_UNCHANGED) item->FSetState(ITEM_CHANGED); // pussywizard: so the item will be saved and owner will be updated in database - item->SetOwnerGUID(rc); + item->SetOwnerGUID(receiverGuid); item->SaveToDB(trans); // recursive and not have transaction guard into self, item not in inventory and can be save standalone draft.AddItem(item); @@ -324,7 +326,7 @@ void WorldSession::HandleSendMail(WorldPacket& recvData) draft .AddMoney(money) .AddCOD(COD) - .SendMailTo(trans, MailReceiver(receive, rc.GetCounter()), MailSender(player), body.empty() ? MAIL_CHECK_MASK_COPIED : MAIL_CHECK_MASK_HAS_BODY, deliver_delay); + .SendMailTo(trans, MailReceiver(receive, receiverGuid.GetCounter()), MailSender(player), body.empty() ? MAIL_CHECK_MASK_COPIED : MAIL_CHECK_MASK_HAS_BODY, deliver_delay); player->SaveInventoryAndGoldToDB(trans); CharacterDatabase.CommitTransaction(trans); @@ -597,7 +599,6 @@ void WorldSession::HandleGetMailList(WorldPacket& recvData) return; Player* player = _player; - player->_LoadMail(); uint8 mailsCount = 0; uint32 realCount = 0; @@ -788,8 +789,6 @@ void WorldSession::HandleQueryNextMailTime(WorldPacket& /*recvData*/) { WorldPacket data(MSG_QUERY_NEXT_MAIL_TIME, 8); - _player->_LoadMail(); - if (_player->unReadMails > 0) { data << float(0); // float