fix(mail): new mail notices in live's form

Live filled a NewMail notice with the mail it was about: the mail ID, the
player, the attachment (LOT -1 without one) and a count of 1, and at load sent
one notice per unread mail. DLU sent one notice with every field 0 and the
unread total as the count. The answer to NotificationRequest names the player
and no mail, with LOT -1 and the unread count, as live's did.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-09-30 02:50:12 -05:00
parent 3f0e9bc336
commit b629766182
5 changed files with 113 additions and 50 deletions

View File

@@ -181,10 +181,12 @@ namespace Mail {
void NotificationResponse::Serialize(RakNet::BitStream& bitStream) const {
MailLUBitStream::Serialize(bitStream);
bitStream.Write(status);
bitStream.Write<uint64_t>(0); // unused
bitStream.Write<uint64_t>(0); // unused
bitStream.Write(auctionID);
bitStream.Write<uint64_t>(0); // unused
bitStream.Write(mailID);
bitStream.Write(receiverID);
bitStream.Write(attachmentID);
bitStream.Write(attachmentLOT);
bitStream.Write(attachmentCount);
bitStream.Write<uint16_t>(0); // packing
bitStream.Write(mailCount);
bitStream.Write<uint32_t>(0); // packing
}
@@ -339,6 +341,7 @@ namespace Mail {
if (character) {
auto unreadMailCount = Database::Get()->GetUnreadMailCount(character->GetID());
response.status = eNotificationResponse::NewMail;
response.receiverID = player->GetObjectID();
response.mailCount = unreadMailCount;
}
@@ -418,7 +421,7 @@ void Mail::SendMail(const LWOOBJID sender, const std::string& senderName, const
void Mail::SendMail(const LWOOBJID sender, const std::string& senderName, LWOOBJID recipient,
const std::string& recipientName, const std::string& subject, const std::string& body, const LOT attachment,
const uint16_t attachmentCount, const SystemAddress& sysAddr) {
const uint16_t attachmentCount, [[maybe_unused]] const SystemAddress& sysAddr) {
MailInfo mailInsert;
mailInsert.senderUsername = senderName;
mailInsert.recipient = recipientName;
@@ -434,21 +437,32 @@ void Mail::SendMail(const LWOOBJID sender, const std::string& senderName, LWOOBJ
Database::Get()->InsertNewMail(mailInsert);
DashboardNotify::Changed("mail", mailInsert.receiverId);
if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) {
NotifyNewMail(recipient);
return;
}
NotificationResponse response;
response.status = eNotificationResponse::NewMail;
response.Send(sysAddr);
NotifyNewMail(recipient);
}
void Mail::NotifyUnreadMailOnLoad(const uint32_t unreadCount, const SystemAddress& sysAddr) {
if (unreadCount == 0) return;
NotificationResponse response;
response.status = eNotificationResponse::NewMail;
response.mailCount = unreadCount;
response.Send(sysAddr);
Mail::NotificationResponse Mail::NewMailNotice(const MailInfo& mail, const LWOOBJID playerID) {
NotificationResponse notice;
notice.status = eNotificationResponse::NewMail;
notice.mailID = mail.id;
notice.receiverID = playerID;
notice.attachmentID = mail.itemID;
notice.attachmentLOT = mail.itemLOT > 0 ? mail.itemLOT : LOT_NULL;
notice.attachmentCount = static_cast<uint16_t>(mail.itemCount);
notice.mailCount = 1;
return notice;
}
std::vector<Mail::NotificationResponse> Mail::UnreadMailNotices(std::vector<MailInfo> mail, const LWOOBJID playerID) {
std::sort(mail.begin(), mail.end(), [](const MailInfo& a, const MailInfo& b) { return a.id < b.id; });
std::vector<NotificationResponse> notices;
for (const auto& entry : mail) {
if (!entry.wasRead) notices.push_back(NewMailNotice(entry, playerID));
}
return notices;
}
void Mail::NotifyUnreadMailOnLoad(const LWOOBJID characterID, const LWOOBJID playerID, const SystemAddress& sysAddr) {
for (const auto& notice : UnreadMailNotices(Database::Get()->GetMailForPlayer(characterID, 20), playerID)) notice.Send(sysAddr);
}
bool Mail::NotifyNewMailHere(LWOOBJID receiver) {
@@ -459,10 +473,13 @@ bool Mail::NotifyNewMailHere(LWOOBJID receiver) {
auto* const character = player->GetCharacter();
if (!character) return false;
NotificationResponse response;
response.status = eNotificationResponse::NewMail;
response.mailCount = Database::Get()->GetUnreadMailCount(character->GetID());
response.Send(player->GetSystemAddress());
// About the newest unread mail, the one that just arrived
const auto mail = Database::Get()->GetMailForPlayer(character->GetID(), 20);
const MailInfo* newest = nullptr;
for (const auto& entry : mail) {
if (!entry.wasRead && (!newest || entry.id > newest->id)) newest = &entry;
}
if (newest) NewMailNotice(*newest, player->GetObjectID()).Send(player->GetSystemAddress());
return true;
}

View File

@@ -2,6 +2,7 @@
#define __MAIL_H__
#include <cstdint>
#include <vector>
#include "BitStream.h"
#include "RakNetTypes.h"
#include "dCommonVars.h"
@@ -131,9 +132,15 @@ namespace Mail {
void Serialize(RakNet::BitStream& bitStream) const override;
};
// Live filled a NewMail notice with the mail it was about (the answer to NotificationRequest named no mail and
// carried the unread count). The client reads the status and mailCount, and the object for auction notices.
struct NotificationResponse : public MailLUBitStream {
eNotificationResponse status = eNotificationResponse::UnknownError;
LWOOBJID auctionID = LWOOBJID_EMPTY;
uint64_t mailID = 0;
LWOOBJID receiverID = LWOOBJID_EMPTY; // the player's object ID
LWOOBJID attachmentID = LWOOBJID_EMPTY;
LOT attachmentLOT = LOT_NULL;
uint16_t attachmentCount = 0;
uint32_t mailCount = 1;
NotificationResponse() : MailLUBitStream(eMessageID::NotificationResponse) {};
void Serialize(RakNet::BitStream& bitStream) const override;
@@ -210,9 +217,15 @@ namespace Mail {
void HandleMail(RakNet::BitStream& inStream, const SystemAddress& sysAddr, Entity* player);
// A NewMail notice about one mail, the way live sent it: the mail, its attachment and a count of 1.
NotificationResponse NewMailNotice(const MailInfo& mail, LWOOBJID playerID);
// Part of a player's load: live told the client about unread mail right after the respawn checkpoint, without
// being asked (NotificationResponse NewMail with the count), and sent nothing when there was none.
void NotifyUnreadMailOnLoad(uint32_t unreadCount, const SystemAddress& sysAddr);
// being asked, one NewMail notice per mail, and sent nothing when there was none.
void NotifyUnreadMailOnLoad(LWOOBJID characterID, LWOOBJID playerID, const SystemAddress& sysAddr);
// The notices NotifyUnreadMailOnLoad sends for this mail: one per unread mail, oldest first.
std::vector<NotificationResponse> UnreadMailNotices(std::vector<MailInfo> mail, LWOOBJID playerID);
// Tells the receiver about their unread mail if they are a player in this world. Returns false if they are not.
bool NotifyNewMailHere(LWOOBJID receiver);