Add mailbox system and fix logging performance stutter

Implement full mail send/receive: SMSG_SHOW_MAILBOX, CMSG_GET_MAIL_LIST,
SMSG_MAIL_LIST_RESULT, CMSG_SEND_MAIL, SMSG_SEND_MAIL_RESULT, mail take
money/item/delete/mark-as-read, and inbox/compose UI windows.

Fix periodic stuttering in Stormwind caused by synchronous per-line disk
flushes in the logger — remove fileStream.flush() and std::endl, downgrade
high-volume per-packet/per-model/per-texture LOG_INFO to LOG_DEBUG.
This commit is contained in:
Kelsi 2026-02-15 14:00:41 -08:00
parent 9bc8c5c85a
commit 8a468e9533
14 changed files with 782 additions and 22 deletions

View file

@ -762,6 +762,23 @@ public:
bool isVendorWindowOpen() const { return vendorWindowOpen; }
const ListInventoryData& getVendorItems() const { return currentVendorItems; }
// Mail
bool isMailboxOpen() const { return mailboxOpen_; }
const std::vector<MailMessage>& getMailInbox() const { return mailInbox_; }
int getSelectedMailIndex() const { return selectedMailIndex_; }
void setSelectedMailIndex(int idx) { selectedMailIndex_ = idx; }
bool isMailComposeOpen() const { return showMailCompose_; }
void openMailCompose() { showMailCompose_ = true; }
void closeMailCompose() { showMailCompose_ = false; }
void closeMailbox();
void sendMail(const std::string& recipient, const std::string& subject,
const std::string& body, uint32_t money, uint32_t cod = 0);
void mailTakeMoney(uint32_t mailId);
void mailTakeItem(uint32_t mailId, uint32_t itemIndex);
void mailDelete(uint32_t mailId);
void mailMarkAsRead(uint32_t mailId);
void refreshMailList();
// Trainer
bool isTrainerWindowOpen() const { return trainerWindowOpen_; }
const TrainerListData& getTrainerSpells() const { return currentTrainerList_; }
@ -987,6 +1004,12 @@ private:
void handleArenaTeamEvent(network::Packet& packet);
void handleArenaError(network::Packet& packet);
// ---- Mail handlers ----
void handleShowMailbox(network::Packet& packet);
void handleMailListResult(network::Packet& packet);
void handleSendMailResult(network::Packet& packet);
void handleReceivedMail(network::Packet& packet);
// ---- Taxi handlers ----
void handleShowTaxiNodes(network::Packet& packet);
void handleActivateTaxiReply(network::Packet& packet);
@ -1322,6 +1345,13 @@ private:
void startClientTaxiPath(const std::vector<uint32_t>& pathNodes);
void updateClientTaxi(float deltaTime);
// Mail
bool mailboxOpen_ = false;
uint64_t mailboxGuid_ = 0;
std::vector<MailMessage> mailInbox_;
int selectedMailIndex_ = -1;
bool showMailCompose_ = false;
// Vendor
bool vendorWindowOpen = false;
ListInventoryData currentVendorItems;

View file

@ -365,6 +365,19 @@ enum class LogicalOpcode : uint16_t {
CMSG_CHANNEL_LIST,
SMSG_CHANNEL_LIST,
// ---- Mail ----
SMSG_SHOW_MAILBOX,
CMSG_GET_MAIL_LIST,
SMSG_MAIL_LIST_RESULT,
CMSG_SEND_MAIL,
SMSG_SEND_MAIL_RESULT,
CMSG_MAIL_TAKE_MONEY,
CMSG_MAIL_TAKE_ITEM,
CMSG_MAIL_DELETE,
CMSG_MAIL_MARK_AS_READ,
SMSG_RECEIVED_MAIL,
MSG_QUERY_NEXT_MAIL_TIME,
// Sentinel
COUNT
};

View file

@ -2166,5 +2166,77 @@ public:
static network::Packet build(uint64_t casterGuid, bool accept);
};
// ============================================================
// Mail System
// ============================================================
struct MailAttachment {
uint8_t slot = 0;
uint32_t itemGuidLow = 0;
uint32_t itemId = 0;
uint32_t enchantId = 0;
uint32_t randomPropertyId = 0;
uint32_t randomSuffix = 0;
uint32_t stackCount = 1;
uint32_t chargesOrDurability = 0;
uint32_t maxDurability = 0;
};
struct MailMessage {
uint32_t messageId = 0;
uint8_t messageType = 0; // 0=normal, 2=auction, 3=creature, 4=gameobject
uint64_t senderGuid = 0;
uint32_t senderEntry = 0; // For non-player mail
std::string senderName;
std::string subject;
std::string body;
uint32_t stationeryId = 0;
uint32_t money = 0;
uint32_t cod = 0; // Cash on delivery
uint32_t flags = 0;
float expirationTime = 0.0f;
uint32_t mailTemplateId = 0;
bool read = false;
std::vector<MailAttachment> attachments;
};
/** CMSG_GET_MAIL_LIST packet builder */
class GetMailListPacket {
public:
static network::Packet build(uint64_t mailboxGuid);
};
/** CMSG_SEND_MAIL packet builder */
class SendMailPacket {
public:
static network::Packet build(uint64_t mailboxGuid, const std::string& recipient,
const std::string& subject, const std::string& body,
uint32_t money, uint32_t cod);
};
/** CMSG_MAIL_TAKE_MONEY packet builder */
class MailTakeMoneyPacket {
public:
static network::Packet build(uint64_t mailboxGuid, uint32_t mailId);
};
/** CMSG_MAIL_TAKE_ITEM packet builder */
class MailTakeItemPacket {
public:
static network::Packet build(uint64_t mailboxGuid, uint32_t mailId, uint32_t itemIndex);
};
/** CMSG_MAIL_DELETE packet builder */
class MailDeletePacket {
public:
static network::Packet build(uint64_t mailboxGuid, uint32_t mailId, uint32_t mailTemplateId);
};
/** CMSG_MAIL_MARK_AS_READ packet builder */
class MailMarkAsReadPacket {
public:
static network::Packet build(uint64_t mailboxGuid, uint32_t mailId);
};
} // namespace game
} // namespace wowee

View file

@ -181,6 +181,8 @@ private:
void renderGuildRoster(game::GameHandler& gameHandler);
void renderGuildInvitePopup(game::GameHandler& gameHandler);
void renderChatBubbles(game::GameHandler& gameHandler);
void renderMailWindow(game::GameHandler& gameHandler);
void renderMailComposeWindow(game::GameHandler& gameHandler);
/**
* Inventory screen
@ -242,6 +244,12 @@ private:
std::vector<ChatBubble> chatBubbles_;
bool chatBubbleCallbackSet_ = false;
// Mail compose state
char mailRecipientBuffer_[256] = "";
char mailSubjectBuffer_[256] = "";
char mailBodyBuffer_[2048] = "";
int mailComposeMoney_[3] = {0, 0, 0}; // gold, silver, copper
// Left-click targeting: distinguish click from camera drag
glm::vec2 leftClickPressPos_ = glm::vec2(0.0f);
bool leftClickWasPress_ = false;