refactor(Core/Mails): centralize mail count bookkeeping in a new MailMgr (#26727)

This commit is contained in:
Andrew
2026-07-23 21:22:08 -03:00
committed by GitHub
parent a8d75f59c5
commit a8c3736879
13 changed files with 346 additions and 168 deletions

View File

@@ -424,7 +424,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, expire_time, deliver_time, money, cod, checked, stationery, mailTemplateId FROM mail WHERE receiver = ? ORDER BY id DESC", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_MAIL, "SELECT id, messageType, sender, receiver, subject, body, expire_time, deliver_time, money, cod, checked, stationery, mailTemplateId, has_items FROM mail WHERE receiver = ? ORDER BY id DESC", 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);

View File

@@ -19,9 +19,12 @@
#include "ArenaTeam.h"
#include "DatabaseEnv.h"
#include "Log.h"
#include "MailMgr.h"
#include "Player.h"
#include "Timer.h"
#include "World.h"
#include <algorithm>
#include <limits>
#include <unordered_map>
namespace
@@ -76,15 +79,7 @@ void CharacterCache::LoadCharacterCacheStorage()
fields[4].Get<uint8>() /*gender*/, fields[3].Get<uint8>() /*race*/, fields[5].Get<uint8>() /*class*/, fields[6].Get<uint8>() /*level*/);
} while (result->NextRow());
QueryResult mailCountResult = CharacterDatabase.Query("SELECT receiver, COUNT(receiver) FROM mail GROUP BY receiver");
if (mailCountResult)
{
do
{
Field* fields = mailCountResult->Fetch();
UpdateCharacterMailCount(ObjectGuid(HighGuid::Player, fields[0].Get<uint32>()), static_cast<int8>(fields[1].Get<uint64>()), true);
} while (mailCountResult->NextRow());
}
sMailMgr->LoadMailCounts();
LOG_INFO("server.loading", ">> Loaded Character Infos For {} Characters in {} ms", _characterCacheStore.size(), GetMSTimeDiffToNow(oldMSTime));
LOG_INFO("server.loading", " ");
@@ -105,15 +100,7 @@ void CharacterCache::RefreshCacheEntry(uint32 lowGuid)
AddCharacterCacheEntry(ObjectGuid::Create<HighGuid::Player>(fields[0].Get<uint32>()) /*guid*/, fields[2].Get<uint32>() /*account*/, fields[1].Get<std::string>() /*name*/, fields[4].Get<uint8>() /*gender*/, fields[3].Get<uint8>() /*race*/, fields[5].Get<uint8>() /*class*/, fields[6].Get<uint8>() /*level*/);
} while (result->NextRow());
QueryResult mailCountResult = CharacterDatabase.Query("SELECT receiver, COUNT(receiver) FROM mail WHERE receiver = {} GROUP BY receiver", lowGuid);
if (mailCountResult)
{
do
{
Field* fields = mailCountResult->Fetch();
UpdateCharacterMailCount(ObjectGuid(HighGuid::Player, fields[0].Get<uint32>()), static_cast<int8>(fields[1].Get<uint64>()), true);
} while (mailCountResult->NextRow());
}
sMailMgr->RecountMailCount(lowGuid);
}
/*
@@ -216,27 +203,25 @@ void CharacterCache::UpdateCharacterArenaTeamId(ObjectGuid const& guid, uint8 sl
itr->second.ArenaTeamId[slot] = arenaTeamId;
}
void CharacterCache::UpdateCharacterMailCount(ObjectGuid const& guid, int8 count, bool update)
void CharacterCache::UpdateCharacterMailCount(ObjectGuid const& guid, int32 count, bool update)
{
auto itr = _characterCacheStore.find(guid);
if (itr == _characterCacheStore.end())
{
return;
}
constexpr int32 maxCount = std::numeric_limits<uint16>::max();
if (update)
{
itr->second.MailCount = count;
itr->second.MailCount = static_cast<uint16>(std::clamp<int32>(count, 0, maxCount));
return;
}
// Let's be safe and prevent overflow
if (!itr->second.MailCount && count < 0)
{
return;
}
int32 newCount = static_cast<int32>(itr->second.MailCount) + count;
if (newCount < 0)
LOG_WARN("entities.player", "CharacterCache::UpdateCharacterMailCount: mail count for {} would go negative ({}), a mail insert was not reported; clamping to 0", guid.ToString(), newCount);
itr->second.MailCount += count;
itr->second.MailCount = static_cast<uint16>(std::clamp(newCount, 0, maxCount));
}
void CharacterCache::UpdateCharacterGroup(ObjectGuid const& guid, ObjectGuid groupGUID)

View File

@@ -33,7 +33,7 @@ struct CharacterCacheEntry
uint8 Race;
uint8 Sex;
uint8 Level;
uint8 MailCount;
uint16 MailCount;
ObjectGuid::LowType GuildId;
std::array<uint32, MAX_ARENA_SLOT> ArenaTeamId;
ObjectGuid GroupGuid;
@@ -58,10 +58,6 @@ class AC_GAME_API CharacterCache
void UpdateCharacterGuildId(ObjectGuid const& guid, ObjectGuid::LowType guildId);
void UpdateCharacterArenaTeamId(ObjectGuid const& guid, uint8 slot, uint32 arenaTeamId);
void UpdateCharacterMailCount(ObjectGuid const& guid, int8 count, bool update = false);
void DecreaseCharacterMailCount(ObjectGuid const& guid) { UpdateCharacterMailCount(guid, -1); };
void IncreaseCharacterMailCount(ObjectGuid const& guid) { UpdateCharacterMailCount(guid, 1); };
[[nodiscard]] bool HasCharacterCacheEntry(ObjectGuid const& guid) const;
[[nodiscard]] CharacterCacheEntry const* GetCharacterCacheByGuid(ObjectGuid const& guid) const;
[[nodiscard]] CharacterCacheEntry const* GetCharacterCacheByName(std::string const& name) const;
@@ -78,6 +74,15 @@ class AC_GAME_API CharacterCache
[[nodiscard]] ObjectGuid::LowType GetCharacterGuildIdByGuid(ObjectGuid guid) const;
[[nodiscard]] uint32 GetCharacterArenaTeamIdByGuid(ObjectGuid guid, uint8 type) const;
[[nodiscard]] ObjectGuid GetCharacterGroupGuidByGuid(ObjectGuid guid) const;
private:
// Only MailMgr may touch the mail count, so every change is paired
// with the matching write to the `mail` table
void UpdateCharacterMailCount(ObjectGuid const& guid, int32 count, bool update = false);
void DecreaseCharacterMailCount(ObjectGuid const& guid) { UpdateCharacterMailCount(guid, -1); }
void IncreaseCharacterMailCount(ObjectGuid const& guid) { UpdateCharacterMailCount(guid, 1); }
friend class MailMgr;
};
#define sCharacterCache CharacterCache::instance()

View File

@@ -40,6 +40,7 @@
#include "Language.h"
#include "Log.h"
#include "LootItemStorage.h"
#include "MailMgr.h"
#include "MapMgr.h"
#include "ObjectAccessor.h"
#include "ObjectMgr.h"
@@ -6298,10 +6299,21 @@ void Player::_LoadMail(PreparedQueryResult mailsResult, PreparedQueryResult mail
m->checked = fields[10].Get<uint8>();
m->stationery = fields[11].Get<uint8>();
m->mailTemplateId = fields[12].Get<int16>();
bool has_items = fields[13].Get<bool>();
if (cur_time > m->expire_time)
{
LOG_DEBUG("entities.player", "Player::_LoadMail: Mail ({}) has expired - ignored.", m->messageID);
// Drop empty expired mail now; mail with items or money is left
// for ReturnOrDeleteOldMails, which owns return-to-sender handling
if (!has_items && !m->money && !m->COD)
{
LOG_DEBUG("entities.player", "Player::_LoadMail: Mail ({}) has expired - deleted.", m->messageID);
sMailMgr->DeleteEmptyExpiredMail(m->messageID, GetGUID().GetCounter());
}
else
LOG_DEBUG("entities.player", "Player::_LoadMail: Mail ({}) has expired - ignored.", m->messageID);
delete m;
continue;
}

View File

@@ -6804,129 +6804,6 @@ void ObjectMgr::LoadNpcTextLocales()
LOG_INFO("server.loading", ">> Loaded {} Npc Text Locale Strings in {} ms", (uint32)_npcTextLocaleStore.size(), GetMSTimeDiffToNow(oldMSTime));
}
void ObjectMgr::ReturnOrDeleteOldMails(bool serverUp)
{
uint32 oldMSTime = getMSTime();
time_t curTime = GameTime::GetGameTime().count();
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_EXPIRED_MAIL);
stmt->SetData(0, uint32(curTime));
PreparedQueryResult result = CharacterDatabase.Query(stmt);
if (!result)
return;
std::map<uint32 /*messageId*/, MailItemInfoVec> itemsCache;
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_EXPIRED_MAIL_ITEMS);
stmt->SetData(0, uint32(curTime));
if (PreparedQueryResult items = CharacterDatabase.Query(stmt))
{
MailItemInfo item;
do
{
Field* fields = items->Fetch();
item.item_guid = fields[0].Get<uint32>();
item.item_template = fields[1].Get<uint32>();
uint32 mailId = fields[2].Get<uint32>();
itemsCache[mailId].push_back(item);
} while (items->NextRow());
}
uint32 deletedCount = 0;
uint32 returnedCount = 0;
do
{
Field* fields = result->Fetch();
Mail* m = new Mail;
m->messageID = fields[0].Get<uint32>();
m->messageType = fields[1].Get<uint8>();
m->sender = fields[2].Get<uint32>();
m->receiver = fields[3].Get<uint32>();
bool has_items = fields[4].Get<bool>();
m->expire_time = time_t(fields[5].Get<uint32>());
m->deliver_time = time_t(0);
m->stationery = fields[6].Get<uint8>();
m->checked = fields[7].Get<uint8>();
m->mailTemplateId = fields[8].Get<int16>();
Player* player = nullptr;
if (serverUp)
player = ObjectAccessor::FindPlayerByLowGUID(m->receiver);
if (player) // don't modify mails of a logged in player
{
delete m;
continue;
}
// Delete or return mail
if (has_items)
{
// read items from cache
m->items.swap(itemsCache[m->messageID]);
// If it is mail from non-player, or if it's already return mail, it shouldn't be returned, but deleted
if (!m->IsSentByPlayer() || m->IsSentByGM() || (m->IsCODPayment() || m->IsReturnedMail()))
{
for (auto const& mailedItem : m->items)
{
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE);
stmt->SetData(0, mailedItem.item_guid);
CharacterDatabase.Execute(stmt);
}
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_ITEM_BY_ID);
stmt->SetData(0, m->messageID);
CharacterDatabase.Execute(stmt);
}
else
{
// Mail will be returned
stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_MAIL_RETURNED);
stmt->SetData(0, m->receiver);
stmt->SetData(1, m->sender);
stmt->SetData(2, uint32(curTime + 30 * DAY));
stmt->SetData(3, uint32(curTime));
stmt->SetData (4, uint8(MAIL_CHECK_MASK_RETURNED));
stmt->SetData(5, m->messageID);
CharacterDatabase.Execute(stmt);
for (auto const& mailedItem : m->items)
{
// Update receiver in mail items for its proper delivery, and in instance_item for avoid lost item at sender delete
stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_MAIL_ITEM_RECEIVER);
stmt->SetData(0, m->sender);
stmt->SetData(1, mailedItem.item_guid);
CharacterDatabase.Execute(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_ITEM_OWNER);
stmt->SetData(0, m->sender);
stmt->SetData(1, mailedItem.item_guid);
CharacterDatabase.Execute(stmt);
}
// xinef: update global data
sCharacterCache->IncreaseCharacterMailCount(ObjectGuid(HighGuid::Player, m->sender));
sCharacterCache->DecreaseCharacterMailCount(ObjectGuid(HighGuid::Player, m->receiver));
delete m;
++returnedCount;
continue;
}
}
sCharacterCache->DecreaseCharacterMailCount(ObjectGuid(HighGuid::Player, m->receiver));
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_BY_ID);
stmt->SetData(0, m->messageID);
CharacterDatabase.Execute(stmt);
delete m;
++deletedCount;
} while (result->NextRow());
LOG_INFO("server.loading", ">> Processed {} expired mails: {} deleted and {} returned in {} ms", deletedCount + returnedCount, deletedCount, returnedCount, GetMSTimeDiffToNow(oldMSTime));
LOG_INFO("server.loading", " ");
}
void ObjectMgr::LoadQuestAreaTriggers()
{
uint32 oldMSTime = getMSTime();

View File

@@ -1129,8 +1129,6 @@ public:
return itr != _fishingBaseForAreaStore.end() ? itr->second : 0;
}
void ReturnOrDeleteOldMails(bool serverUp);
CreatureBaseStats const* GetCreatureBaseStats(uint8 level, uint8 unitClass);
void SetHighestGuids();

View File

@@ -26,6 +26,7 @@
#include "Language.h"
#include "Log.h"
#include "Mail.h"
#include "MailMgr.h"
#include "ObjectMgr.h"
#include "Opcodes.h"
#include "Player.h"
@@ -416,7 +417,7 @@ void WorldSession::HandleMailDelete(WorldPacket& recvData)
Mail* m = _player->GetMail(mailId);
Player* player = _player;
player->m_mailsUpdated = true;
if (m)
if (m && m->state != MAIL_STATE_DELETED)
{
// delete shouldn't show up for COD mails
if (m->COD)
@@ -427,7 +428,7 @@ void WorldSession::HandleMailDelete(WorldPacket& recvData)
m->state = MAIL_STATE_DELETED;
sCharacterCache->DecreaseCharacterMailCount(player->GetGUID());
sMailMgr->OnMailDeleted(player->GetGUID().GetCounter());
}
player->SendMailResult(mailId, MAIL_DELETED, MAIL_OK);
}
@@ -509,7 +510,7 @@ void WorldSession::HandleMailReturnToSender(WorldPacket& recvData)
delete m; //we can deallocate old mail
player->SendMailResult(mailId, MAIL_RETURNED_TO_SENDER, MAIL_OK);
sCharacterCache->DecreaseCharacterMailCount(player->GetGUID());
sMailMgr->OnMailDeleted(player->GetGUID().GetCounter());
}
//called when player takes item attached in mail

View File

@@ -24,6 +24,7 @@
#include "GameTime.h"
#include "Item.h"
#include "Log.h"
#include "MailMgr.h"
#include "ObjectMgr.h"
#include "Player.h"
#include "ScriptMgr.h"
@@ -251,7 +252,7 @@ void MailDraft::SendMailTo(CharacterDatabaseTransaction trans, MailReceiver cons
trans->Append(stmt);
}
sCharacterCache->IncreaseCharacterMailCount(ObjectGuid(HighGuid::Player, receiver.GetPlayerGUIDLow()));
sMailMgr->OnMailSent(receiver.GetPlayerGUIDLow());
// For online receiver update in game mail status and data
if (pReceiver)

View File

@@ -0,0 +1,209 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "MailMgr.h"
#include "CharacterCache.h"
#include "Common.h"
#include "DatabaseEnv.h"
#include "GameTime.h"
#include "Log.h"
#include "Mail.h"
#include "ObjectAccessor.h"
#include "Timer.h"
#include <map>
MailMgr* MailMgr::instance()
{
static MailMgr instance;
return &instance;
}
void MailMgr::OnMailSent(ObjectGuid::LowType receiverLow)
{
sCharacterCache->IncreaseCharacterMailCount(ObjectGuid(HighGuid::Player, receiverLow));
}
void MailMgr::OnMailDeleted(ObjectGuid::LowType receiverLow)
{
sCharacterCache->DecreaseCharacterMailCount(ObjectGuid(HighGuid::Player, receiverLow));
}
void MailMgr::OnMailReturned(ObjectGuid::LowType oldReceiverLow, ObjectGuid::LowType newReceiverLow)
{
sCharacterCache->DecreaseCharacterMailCount(ObjectGuid(HighGuid::Player, oldReceiverLow));
sCharacterCache->IncreaseCharacterMailCount(ObjectGuid(HighGuid::Player, newReceiverLow));
}
void MailMgr::LoadMailCounts()
{
QueryResult result = CharacterDatabase.Query("SELECT receiver, COUNT(receiver) FROM mail GROUP BY receiver");
if (!result)
return;
do
{
Field* fields = result->Fetch();
sCharacterCache->UpdateCharacterMailCount(ObjectGuid(HighGuid::Player, fields[0].Get<uint32>()), static_cast<int32>(fields[1].Get<uint64>()), true);
} while (result->NextRow());
}
void MailMgr::RecountMailCount(ObjectGuid::LowType receiverLow)
{
int32 count = 0;
if (QueryResult result = CharacterDatabase.Query("SELECT COUNT(*) FROM mail WHERE receiver = {}", receiverLow))
count = static_cast<int32>((*result)[0].Get<uint64>());
sCharacterCache->UpdateCharacterMailCount(ObjectGuid(HighGuid::Player, receiverLow), count, true);
}
void MailMgr::DeleteEmptyExpiredMail(uint32 mailId, ObjectGuid::LowType receiverLow)
{
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_BY_ID);
stmt->SetData(0, mailId);
CharacterDatabase.Execute(stmt);
OnMailDeleted(receiverLow);
}
void MailMgr::ReturnOrDeleteOldMails(bool serverUp)
{
uint32 oldMSTime = getMSTime();
time_t curTime = GameTime::GetGameTime().count();
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_EXPIRED_MAIL);
stmt->SetData(0, uint32(curTime));
PreparedQueryResult result = CharacterDatabase.Query(stmt);
if (!result)
return;
std::map<uint32 /*messageId*/, MailItemInfoVec> itemsCache;
stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_EXPIRED_MAIL_ITEMS);
stmt->SetData(0, uint32(curTime));
if (PreparedQueryResult items = CharacterDatabase.Query(stmt))
{
MailItemInfo item;
do
{
Field* fields = items->Fetch();
item.item_guid = fields[0].Get<uint32>();
item.item_template = fields[1].Get<uint32>();
uint32 mailId = fields[2].Get<uint32>();
itemsCache[mailId].push_back(item);
} while (items->NextRow());
}
uint32 deletedCount = 0;
uint32 returnedCount = 0;
do
{
Field* fields = result->Fetch();
Mail* m = new Mail;
m->messageID = fields[0].Get<uint32>();
m->messageType = fields[1].Get<uint8>();
m->sender = fields[2].Get<uint32>();
m->receiver = fields[3].Get<uint32>();
bool has_items = fields[4].Get<bool>();
m->expire_time = time_t(fields[5].Get<uint32>());
m->deliver_time = time_t(0);
m->stationery = fields[6].Get<uint8>();
m->checked = fields[7].Get<uint8>();
m->mailTemplateId = fields[8].Get<int16>();
Player* player = nullptr;
if (serverUp)
player = ObjectAccessor::FindPlayerByLowGUID(m->receiver);
if (player) // don't modify mails of a logged in player
{
delete m;
continue;
}
// Keep each mail's correlated writes atomic
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
// Delete or return mail
if (has_items)
{
// read items from cache
m->items.swap(itemsCache[m->messageID]);
// If it is mail from non-player, or if it's already return mail, it shouldn't be returned, but deleted
if (!m->IsSentByPlayer() || m->IsSentByGM() || (m->IsCODPayment() || m->IsReturnedMail()))
{
for (auto const& mailedItem : m->items)
{
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_ITEM_INSTANCE);
stmt->SetData(0, mailedItem.item_guid);
trans->Append(stmt);
}
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_ITEM_BY_ID);
stmt->SetData(0, m->messageID);
trans->Append(stmt);
}
else
{
// Mail will be returned
stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_MAIL_RETURNED);
stmt->SetData(0, m->receiver);
stmt->SetData(1, m->sender);
stmt->SetData(2, uint32(curTime + 30 * DAY));
stmt->SetData(3, uint32(curTime));
stmt->SetData(4, uint8(MAIL_CHECK_MASK_RETURNED));
stmt->SetData(5, m->messageID);
trans->Append(stmt);
for (auto const& mailedItem : m->items)
{
// Update receiver in mail items for its proper delivery, and in instance_item for avoid lost item at sender delete
stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_MAIL_ITEM_RECEIVER);
stmt->SetData(0, m->sender);
stmt->SetData(1, mailedItem.item_guid);
trans->Append(stmt);
stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_ITEM_OWNER);
stmt->SetData(0, m->sender);
stmt->SetData(1, mailedItem.item_guid);
trans->Append(stmt);
}
CharacterDatabase.CommitTransaction(trans);
OnMailReturned(m->receiver, m->sender);
delete m;
++returnedCount;
continue;
}
}
stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_BY_ID);
stmt->SetData(0, m->messageID);
trans->Append(stmt);
CharacterDatabase.CommitTransaction(trans);
OnMailDeleted(m->receiver);
delete m;
++deletedCount;
} while (result->NextRow());
LOG_INFO("server.loading", ">> Processed {} expired mails: {} deleted and {} returned in {} ms", deletedCount + returnedCount, deletedCount, returnedCount, GetMSTimeDiffToNow(oldMSTime));
LOG_INFO("server.loading", " ");
}

View File

@@ -0,0 +1,87 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _MAILMGR_H
#define _MAILMGR_H
#include "Define.h"
#include "ObjectGuid.h"
/**
* @brief Owns the mail lifecycle bookkeeping that lives outside a single player
* session: the per-character mail count mirrored in CharacterCache and the
* cleanup of expired mail.
*
* Every code path that inserts or deletes a row in the characters `mail` table
* must report it here, otherwise the cached count drifts until the next recount.
*/
class AC_GAME_API MailMgr
{
public:
static MailMgr* instance();
/**
* @brief Reports a mail row inserted for a character.
* @param receiverLow Low GUID of the mail receiver
*/
void OnMailSent(ObjectGuid::LowType receiverLow);
/**
* @brief Reports a mail row deleted from a character's mailbox.
* @param receiverLow Low GUID of the mail receiver
*/
void OnMailDeleted(ObjectGuid::LowType receiverLow);
/**
* @brief Reports a mail row handed to a new receiver (return to sender).
* @param oldReceiverLow Low GUID of the previous receiver
* @param newReceiverLow Low GUID of the new receiver
*/
void OnMailReturned(ObjectGuid::LowType oldReceiverLow, ObjectGuid::LowType newReceiverLow);
/**
* @brief Recounts the mail of all characters from the database.
* Called once at startup after the character cache is filled.
*/
void LoadMailCounts();
/**
* @brief Recounts one character's mail from the database, overwriting the
* cached value.
* @param receiverLow Low GUID of the character to recount
*/
void RecountMailCount(ObjectGuid::LowType receiverLow);
/**
* @brief Deletes an expired mail row that has no items, money or COD
* attached. Used at login for mail that would otherwise stay invisible in
* the DB until ReturnOrDeleteOldMails catches the receiver offline.
* @param mailId Id of the mail row to delete
* @param receiverLow Low GUID of the mail receiver
*/
void DeleteEmptyExpiredMail(uint32 mailId, ObjectGuid::LowType receiverLow);
/**
* @brief Returns expired mail with items to the sender and deletes the rest.
* @param serverUp When true, receivers that are currently online are skipped.
*/
void ReturnOrDeleteOldMails(bool serverUp);
};
#define sMailMgr MailMgr::instance()
#endif

View File

@@ -61,6 +61,7 @@
#include "LootItemStorage.h"
#include "LootMgr.h"
#include "M2Stores.h"
#include "MailMgr.h"
#include "MapMgr.h"
#include "Metric.h"
#include "MotdMgr.h"
@@ -847,7 +848,7 @@ void World::SetInitialWorldSettings()
///- Handle outdated emails (delete/return)
LOG_INFO("server.loading", "Returning Old Mails...");
LOG_INFO("server.loading", " ");
sObjectMgr->ReturnOrDeleteOldMails(false);
sMailMgr->ReturnOrDeleteOldMails(false);
///- Load AutoBroadCast
LOG_INFO("server.loading", "Loading Autobroadcasts...");
@@ -1196,7 +1197,7 @@ void World::Update(uint32 diff)
if (currentGameTime > _mail_expire_check_timer)
{
sObjectMgr->ReturnOrDeleteOldMails(true);
sMailMgr->ReturnOrDeleteOldMails(true);
_mail_expire_check_timer = currentGameTime + 6h;
}

View File

@@ -19,6 +19,7 @@
#include "CommandScript.h"
#include "Group.h"
#include "Language.h"
#include "MailMgr.h"
#include "Player.h"
#include "RBAC.h"
@@ -122,7 +123,7 @@ public:
sCharacterCache->UpdateCharacterAccountId(cPlayer->GetGUID(), cPlayer->GetSession()->GetAccountId());
sCharacterCache->UpdateCharacterGuildId(cPlayer->GetGUID(), cPlayer->GetGuildId());
sCharacterCache->UpdateCharacterMailCount(cPlayer->GetGUID(), cPlayer->GetMailSize(), true);
sMailMgr->RecountMailCount(cPlayer->GetGUID().GetCounter());
sCharacterCache->UpdateCharacterArenaTeamId(cPlayer->GetGUID(), ARENA_SLOT_2v2, cPlayer->GetArenaTeamId(ARENA_SLOT_2v2));
sCharacterCache->UpdateCharacterArenaTeamId(cPlayer->GetGUID(), ARENA_SLOT_3v3, cPlayer->GetArenaTeamId(ARENA_SLOT_3v3));
sCharacterCache->UpdateCharacterArenaTeamId(cPlayer->GetGUID(), ARENA_SLOT_5v5, cPlayer->GetArenaTeamId(ARENA_SLOT_5v5));

View File

@@ -23,6 +23,7 @@
#include "GameTime.h"
#include "Language.h"
#include "Mail.h"
#include "MailMgr.h"
#include "ObjectAccessor.h"
#include "ObjectMgr.h"
#include "Player.h"
@@ -391,7 +392,7 @@ public:
CharacterDatabase.CommitTransaction(trans);
sCharacterCache->DecreaseCharacterMailCount(ObjectGuid(HighGuid::Player, receiver));
sMailMgr->OnMailDeleted(receiver);
handler->PSendSysMessage(LANG_MAIL_RETURN_SUCCESS, mailId, handler->playerLink(target.GetName()));
return true;