mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-27 06:57:15 +00:00
feat(mail): notify online recipients of new mail on any world
Mail sent to someone who is not in the sender's world (system mail with no address, and all player-to-player mail) now reaches them: the world sends a MailNotify (Chat::MAIL, unused until now) to the chat server, which passes it to the world the receiver is in, and that world sends the client its unread count with a NewMail NotificationResponse. Receivers in the same world are told directly. Player-to-player mail did not notify the receiver at all before. Replaces the TODO in Mail::SendMail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -269,6 +269,14 @@ namespace {
|
|||||||
handlers.On<FindPlayerRequest>(Chat::WHO, ChatPacketHandler::HandleWho);
|
handlers.On<FindPlayerRequest>(Chat::WHO, ChatPacketHandler::HandleWho);
|
||||||
handlers.On<ShowAllRequest>(Chat::SHOW_ALL, ChatPacketHandler::HandleShowAll);
|
handlers.On<ShowAllRequest>(Chat::SHOW_ALL, ChatPacketHandler::HandleShowAll);
|
||||||
handlers.On<AchievementNotify>(Chat::ACHIEVEMENT_NOTIFY, ChatPacketHandler::OnAchievementNotify);
|
handlers.On<AchievementNotify>(Chat::ACHIEVEMENT_NOTIFY, ChatPacketHandler::OnAchievementNotify);
|
||||||
|
// New mail: pass it on to the world the receiver is in, if they are online
|
||||||
|
handlers.On<MailNotify>(Chat::MAIL, [](const MailNotify& notify, const SystemAddress&) {
|
||||||
|
const auto& receiver = Game::playerContainer.GetPlayerData(notify.receiverID);
|
||||||
|
if (!receiver) return;
|
||||||
|
RakNet::BitStream bitStream;
|
||||||
|
notify.WritePacket(bitStream);
|
||||||
|
Game::server->Send(bitStream, receiver.worldServerSysAddr, false);
|
||||||
|
});
|
||||||
return handlers;
|
return handlers;
|
||||||
}();
|
}();
|
||||||
return handlers;
|
return handlers;
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
#include "Item.h"
|
#include "Item.h"
|
||||||
#include "MissionComponent.h"
|
#include "MissionComponent.h"
|
||||||
#include "ChatPackets.h"
|
#include "ChatPackets.h"
|
||||||
|
#include "ChatServerLink.h"
|
||||||
|
#include "eObjectBits.h"
|
||||||
#include "Character.h"
|
#include "Character.h"
|
||||||
#include "dZoneManager.h"
|
#include "dZoneManager.h"
|
||||||
#include "WorldConfig.h"
|
#include "WorldConfig.h"
|
||||||
@@ -149,6 +151,7 @@ namespace Mail {
|
|||||||
|
|
||||||
Database::Get()->InsertNewMail(mailInfo);
|
Database::Get()->InsertNewMail(mailInfo);
|
||||||
DashboardNotify::Changed("mail", mailInfo.receiverId);
|
DashboardNotify::Changed("mail", mailInfo.receiverId);
|
||||||
|
NotifyNewMail(mailInfo.receiverId);
|
||||||
if (hasAttachment) {
|
if (hasAttachment) {
|
||||||
EconomyLedger::RecordTransfer({ .method = IEconomyLedger::eTransferMethod::MAIL_SENT, .itemId = sentItemId,
|
EconomyLedger::RecordTransfer({ .method = IEconomyLedger::eTransferMethod::MAIL_SENT, .itemId = sentItemId,
|
||||||
.newItemId = mailInfo.itemID, .lot = mailInfo.itemLOT, .count = static_cast<uint32_t>(mailInfo.itemCount),
|
.newItemId = mailInfo.itemID, .lot = mailInfo.itemLOT, .count = static_cast<uint32_t>(mailInfo.itemCount),
|
||||||
@@ -431,8 +434,35 @@ void Mail::SendMail(const LWOOBJID sender, const std::string& senderName, LWOOBJ
|
|||||||
Database::Get()->InsertNewMail(mailInsert);
|
Database::Get()->InsertNewMail(mailInsert);
|
||||||
DashboardNotify::Changed("mail", mailInsert.receiverId);
|
DashboardNotify::Changed("mail", mailInsert.receiverId);
|
||||||
|
|
||||||
if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) return; // TODO: Echo to chat server
|
if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) {
|
||||||
|
NotifyNewMail(recipient);
|
||||||
|
return;
|
||||||
|
}
|
||||||
NotificationResponse response;
|
NotificationResponse response;
|
||||||
response.status = eNotificationResponse::NewMail;
|
response.status = eNotificationResponse::NewMail;
|
||||||
response.Send(sysAddr);
|
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);
|
||||||
|
auto* const player = Game::entityManager->GetEntity(receiver);
|
||||||
|
if (!player || !player->IsPlayer()) return false;
|
||||||
|
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());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mail::NotifyNewMail(const LWOOBJID receiver) {
|
||||||
|
if (NotifyNewMailHere(receiver)) return;
|
||||||
|
|
||||||
|
ChatPackets::MailNotify notify;
|
||||||
|
notify.receiverID = receiver;
|
||||||
|
GeneralUtils::SetBit(notify.receiverID, eObjectBits::CHARACTER);
|
||||||
|
ChatServerLink::Send(notify, MEDIUM_PRIORITY);
|
||||||
|
}
|
||||||
|
|||||||
@@ -210,6 +210,13 @@ namespace Mail {
|
|||||||
|
|
||||||
void HandleMail(RakNet::BitStream& inStream, const SystemAddress& sysAddr, Entity* player);
|
void HandleMail(RakNet::BitStream& inStream, const SystemAddress& sysAddr, Entity* player);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// Tells the receiver about their new mail wherever they are online: in this world directly, otherwise through the
|
||||||
|
// chat server to the world they are in.
|
||||||
|
void NotifyNewMail(LWOOBJID receiver);
|
||||||
|
|
||||||
void SendMail(
|
void SendMail(
|
||||||
const Entity* recipient,
|
const Entity* recipient,
|
||||||
const std::string& subject,
|
const std::string& subject,
|
||||||
|
|||||||
@@ -168,6 +168,15 @@ namespace ChatPackets {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MailNotify::Serialize(RakNet::BitStream& bitStream) const {
|
||||||
|
bitStream.Write(receiverID);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MailNotify::Deserialize(RakNet::BitStream& bitStream) {
|
||||||
|
VALIDATE_READ(bitStream.Read(receiverID));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void ShowAllRequest::Serialize(RakNet::BitStream& bitStream) const {
|
void ShowAllRequest::Serialize(RakNet::BitStream& bitStream) const {
|
||||||
bitStream.Write(requestor);
|
bitStream.Write(requestor);
|
||||||
bitStream.Write(displayZoneData);
|
bitStream.Write(displayZoneData);
|
||||||
|
|||||||
@@ -126,6 +126,15 @@ namespace ChatPackets {
|
|||||||
bool Deserialize(RakNet::BitStream& bitStream) override;
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// World -> chat -> world: receiverID has new mail. DLU's own server packet: the chat server passes it on to the world
|
||||||
|
// the receiver is in, which sends them the new-mail notification. Characters that are offline are skipped.
|
||||||
|
struct MailNotify : public LUBitStream {
|
||||||
|
LWOOBJID receiverID{ LWOOBJID_EMPTY };
|
||||||
|
MailNotify() : LUBitStream(ServiceType::CHAT, MessageType::Chat::MAIL) {}
|
||||||
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
||||||
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||||
|
};
|
||||||
|
|
||||||
// World -> chat: /showall
|
// World -> chat: /showall
|
||||||
struct ShowAllRequest : public LUBitStream {
|
struct ShowAllRequest : public LUBitStream {
|
||||||
LWOOBJID requestor = LWOOBJID_EMPTY;
|
LWOOBJID requestor = LWOOBJID_EMPTY;
|
||||||
|
|||||||
@@ -623,6 +623,11 @@ namespace {
|
|||||||
Game::server->Send(bitStream, sysAddr, false); //send routed packet to player
|
Game::server->Send(bitStream, sysAddr, false); //send routed packet to player
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// New mail for a player the chat server says is in this world
|
||||||
|
handlers.On<ChatPackets::MailNotify>(MessageType::Chat::MAIL, [](const ChatPackets::MailNotify& notify, const SystemAddress&) {
|
||||||
|
Mail::NotifyNewMailHere(notify.receiverID);
|
||||||
|
});
|
||||||
|
|
||||||
handlers.On<ChatPackets::Announcement>(MessageType::Chat::GM_ANNOUNCE, [](const ChatPackets::Announcement& announcement, const SystemAddress&) {
|
handlers.On<ChatPackets::Announcement>(MessageType::Chat::GM_ANNOUNCE, [](const ChatPackets::Announcement& announcement, const SystemAddress&) {
|
||||||
//Send to our clients:
|
//Send to our clients:
|
||||||
AMFArrayValue args;
|
AMFArrayValue args;
|
||||||
|
|||||||
@@ -898,3 +898,12 @@ TEST_F(ChatPacketsTests, AchievementNotifyRoundTrips) {
|
|||||||
EXPECT_EQ(copy.targetPlayerName.string, u"Friend");
|
EXPECT_EQ(copy.targetPlayerName.string, u"Friend");
|
||||||
ExpectTruncatedFails(notify);
|
ExpectTruncatedFails(notify);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DLU's own world -> chat -> world new-mail notice: header (chat service, MAIL) and the receiver's object ID.
|
||||||
|
TEST_F(ChatPacketsTests, MailNotifyRoundTrips) {
|
||||||
|
ChatPackets::MailNotify notify;
|
||||||
|
notify.receiverID = 0x1000000000000042LL;
|
||||||
|
EXPECT_PACKET_EQ(FromHex("53 02 00 24 00 00 00 00 42 00 00 00 00 00 00 10"), StructPacket(notify));
|
||||||
|
EXPECT_EQ(RoundTrip(notify).receiverID, notify.receiverID);
|
||||||
|
ExpectTruncatedFails(notify);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user