mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 02:43:44 +00:00
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:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1228,7 +1228,7 @@ void LoadPlayer(const SystemAddress& sysAddr) {
|
||||
invalidTransferList.bCustomerFeedbackOnInvalidMapTransfer = false;
|
||||
invalidTransferList.SendToClient(sysAddr);
|
||||
GameMessages::SendDoneLoading(player->GetObjectID(), respawnPoint, spawnPosition, spawnRotation, sysAddr);
|
||||
Mail::NotifyUnreadMailOnLoad(Database::Get()->GetUnreadMailCount(c->GetID()), sysAddr);
|
||||
Mail::NotifyUnreadMailOnLoad(c->GetID(), player->GetObjectID(), sysAddr);
|
||||
|
||||
//Notify chat that a player has loaded:
|
||||
auto* character = player->GetCharacter();
|
||||
|
||||
@@ -129,23 +129,40 @@ TEST(MailPacketTests, NoAttachmentIsLotNull) {
|
||||
EXPECT_EQ(lot, -1);
|
||||
}
|
||||
|
||||
// Answer to NotificationRequest: 56 bytes, the unread count at offset 48 (live sent 2 there for two unread mails).
|
||||
TEST(MailPacketTests, NotificationResponseLayout) {
|
||||
// The answer to NotificationRequest names no mail: the player, no attachment (LOT -1) and the unread count at
|
||||
// offset 48. Live capture (its padding held stale memory; zero here).
|
||||
TEST(MailPacketTests, NotificationRequestAnswerMatchesLive) {
|
||||
Mail::NotificationResponse response;
|
||||
response.status = Mail::eNotificationResponse::NewMail;
|
||||
response.receiverID = 0x100000015b7b141f;
|
||||
response.mailCount = 2;
|
||||
const auto actual = Write(response);
|
||||
ASSERT_EQ(actual.bytes.size(), 56u);
|
||||
Bytes expected;
|
||||
PutClientMailHeader(expected, Mail::eMessageID::NotificationResponse);
|
||||
expected.Put<uint32_t>(0); // NewMail
|
||||
expected.Put<uint64_t>(0);
|
||||
expected.Put<uint64_t>(0);
|
||||
expected.Put<uint64_t>(0);
|
||||
expected.Put<uint64_t>(0);
|
||||
expected.Put<uint32_t>(2); // mail count
|
||||
expected.Put<uint32_t>(0);
|
||||
EXPECT_TRUE(PacketsEqual(expected.Packet(), actual));
|
||||
EXPECT_TRUE(PacketsEqual(
|
||||
FromHex("530500310000000002000000000000000000000000000000""1f147b5b010000100000000000000000ffffffff0000000002000000""00000000"),
|
||||
Write(response)));
|
||||
}
|
||||
|
||||
// A new mail notice: the mail, the player, the attachment and a count of 1. Live capture (padding zeroed).
|
||||
TEST(MailPacketTests, NewMailNoticeMatchesLive) {
|
||||
MailInfo mail;
|
||||
mail.id = 0x10000001733377d7;
|
||||
mail.itemID = 0x10000001733377d6;
|
||||
mail.itemLOT = 0x3e6c;
|
||||
mail.itemCount = 1;
|
||||
EXPECT_TRUE(PacketsEqual(
|
||||
FromHex("53050031000000000200000000000000d7773373010000105e7dea8e00000010d6773373010000106c3e00000100000001000000""00000000"),
|
||||
Write(Mail::NewMailNotice(mail, 0x100000008eea7d5e))));
|
||||
}
|
||||
|
||||
// Without an attachment the notice carries LOT -1, as live did for mission mail.
|
||||
TEST(MailPacketTests, NewMailNoticeWithoutAttachment) {
|
||||
MailInfo mail;
|
||||
mail.id = 7;
|
||||
mail.itemLOT = 0;
|
||||
mail.itemCount = 1;
|
||||
const auto notice = Mail::NewMailNotice(mail, 0x100000015b7b141f);
|
||||
EXPECT_EQ(notice.attachmentLOT, LOT_NULL);
|
||||
EXPECT_EQ(notice.mailCount, 1u);
|
||||
EXPECT_EQ(Write(notice).bytes.size(), 56u);
|
||||
}
|
||||
|
||||
// The mailbox opens the Mail UI with pushGameState {state: "Mail"}; bytes from a live capture.
|
||||
|
||||
@@ -65,18 +65,34 @@ TEST_F(LoadSequenceTests, SavedCheckpointIsSentUnrotated) {
|
||||
EXPECT_EQ(checkpoints[0].rot, QuatUtils::IDENTITY);
|
||||
}
|
||||
|
||||
// Unread mail is announced during the load without the client asking; nothing is sent without unread mail.
|
||||
// Live's packet (56 bytes) also fills the three fields DLU leaves 0 with object IDs the client does not use here.
|
||||
// Unread mail is announced during the load without the client asking, one NewMail notice per unread mail (oldest
|
||||
// first, a count of 1 each, as live sent two notices for two mails); nothing is sent without unread mail.
|
||||
TEST_F(LoadSequenceTests, UnreadMailIsAnnouncedOnLoad) {
|
||||
auto sent = Capture([&] { Mail::NotifyUnreadMailOnLoad(0, ClientAddress()); });
|
||||
EXPECT_TRUE(sent.empty());
|
||||
EXPECT_TRUE(Mail::UnreadMailNotices({}, PLAYER).empty());
|
||||
|
||||
sent = Capture([&] { Mail::NotifyUnreadMailOnLoad(1, ClientAddress()); });
|
||||
ASSERT_EQ(sent.size(), 1u);
|
||||
EXPECT_EQ(sent[0].sysAddr, ClientAddress());
|
||||
EXPECT_PACKET_EQ(FromHex(
|
||||
"53 05 00 31 00 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 "
|
||||
"00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00"), FromCapture(sent[0]));
|
||||
MailInfo read;
|
||||
read.id = 1;
|
||||
read.wasRead = true;
|
||||
MailInfo newer;
|
||||
newer.id = 9;
|
||||
newer.itemLOT = 0;
|
||||
newer.itemCount = 1;
|
||||
MailInfo older;
|
||||
older.id = 4;
|
||||
older.itemID = 0x1000000000000123;
|
||||
older.itemLOT = 3038;
|
||||
older.itemCount = 100;
|
||||
const auto notices = Mail::UnreadMailNotices({ read, newer, older }, PLAYER);
|
||||
ASSERT_EQ(notices.size(), 2u);
|
||||
EXPECT_EQ(notices[0].mailID, 4u);
|
||||
EXPECT_EQ(notices[0].attachmentLOT, 3038);
|
||||
EXPECT_EQ(notices[1].mailID, 9u);
|
||||
EXPECT_EQ(notices[1].attachmentLOT, LOT_NULL);
|
||||
for (const auto& notice : notices) {
|
||||
EXPECT_EQ(notice.status, Mail::eNotificationResponse::NewMail);
|
||||
EXPECT_EQ(notice.receiverID, PLAYER);
|
||||
EXPECT_EQ(notice.mailCount, 1u);
|
||||
}
|
||||
}
|
||||
|
||||
// Live, right before TRANSFER_TO_WORLD on a rocket launch to Nimbus Station (spawn point MedPropLand): flag 32 on,
|
||||
|
||||
Reference in New Issue
Block a user