From 41ecf9d5d80f304424fcf8a47c545ee9d00ba54c Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Tue, 29 Sep 2026 03:33:17 -0500 Subject: [PATCH] feat(load): unread mail is announced during the load Live pushed a mail NotificationResponse (NewMail, the unread count) right after the respawn checkpoint in loads where the player had unread mail, without the client asking (e.g. a new character's first load: ServerDoneLoadingAllObjects, PlayerReachedRespawnCheckpoint, NotifyMissionTask, MAIL). DLU had that call commented out, so the mailbox icon only lit up once the client asked or new mail arrived. It is sent only when there is unread mail (inferred: the 223 captured loads without one are loads without unread mail; only 3 of 226 loads carry it). Check: send a character mail, log it out and back in without opening the mailbox: the new-mail indicator shows right after loading; a character with no unread mail shows none. Co-Authored-By: Claude Opus 5.5 --- dGame/dUtilities/Mail.cpp | 8 ++++++++ dGame/dUtilities/Mail.h | 4 ++++ dWorldServer/WorldServer.cpp | 5 +---- .../dGameMessagesTests/LoadSequenceTests.cpp | 15 +++++++++++++++ 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/dGame/dUtilities/Mail.cpp b/dGame/dUtilities/Mail.cpp index e7630ed6c..6254954c1 100644 --- a/dGame/dUtilities/Mail.cpp +++ b/dGame/dUtilities/Mail.cpp @@ -443,6 +443,14 @@ void Mail::SendMail(const LWOOBJID sender, const std::string& senderName, LWOOBJ response.Send(sysAddr); } +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); +} + bool Mail::NotifyNewMailHere(LWOOBJID receiver) { // Mail stores the character ID; the player's object ID also carries the character bit GeneralUtils::SetBit(receiver, eObjectBits::CHARACTER); diff --git a/dGame/dUtilities/Mail.h b/dGame/dUtilities/Mail.h index 3fdc8084c..aaab9b4a9 100644 --- a/dGame/dUtilities/Mail.h +++ b/dGame/dUtilities/Mail.h @@ -210,6 +210,10 @@ namespace Mail { void HandleMail(RakNet::BitStream& inStream, const SystemAddress& sysAddr, Entity* player); + // 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); + // 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); diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index 9dd673c96..2a4529c0a 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -1136,10 +1136,7 @@ void LoadPlayer(const SystemAddress& sysAddr) { invalidTransferList.bCustomerFeedbackOnInvalidMapTransfer = false; invalidTransferList.SendToClient(sysAddr); GameMessages::SendDoneLoading(player->GetObjectID(), respawnPoint, spawnPosition, spawnRotation, sysAddr); - - //Send the player it's mail count: - //update: this might not be needed so im going to try disabling this here. - //Mail::HandleNotificationRequest(sysAddr, player->GetObjectID()); + Mail::NotifyUnreadMailOnLoad(Database::Get()->GetUnreadMailCount(c->GetID()), sysAddr); //Notify chat that a player has loaded: auto* character = player->GetCharacter(); diff --git a/tests/dGameTests/dGameMessagesTests/LoadSequenceTests.cpp b/tests/dGameTests/dGameMessagesTests/LoadSequenceTests.cpp index fb88cf906..ac96042c1 100644 --- a/tests/dGameTests/dGameMessagesTests/LoadSequenceTests.cpp +++ b/tests/dGameTests/dGameMessagesTests/LoadSequenceTests.cpp @@ -1,6 +1,7 @@ #include "GameDependencies.h" #include "GameMessageTestUtils.h" +#include "Mail.h" #include "MovementMessages.h" #include "ZoneMessages.h" @@ -63,3 +64,17 @@ TEST_F(LoadSequenceTests, SavedCheckpointIsSentUnrotated) { EXPECT_EQ(checkpoints[0].pos, saved); 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. +TEST_F(LoadSequenceTests, UnreadMailIsAnnouncedOnLoad) { + auto sent = Capture([&] { Mail::NotifyUnreadMailOnLoad(0, ClientAddress()); }); + EXPECT_TRUE(sent.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])); +}