mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 19:03:43 +00:00
Every packet of the chat service is now an LUBitStream struct in dNet/ChatPackets.h (docs/PacketArchitecture.md, PR 13), and nothing in the chat server or the chat side of the world server reads or writes a packet by hand any more. - World <-> chat: LoginSessionNotify, UnexpectedDisconnect, GMLevelUpdate, GMMute, Announcement (GM announce), CreateTeam, TeamUpdate (TEAM_GET_STATUS to worlds), AchievementNotify, ShowAllRequest, FindPlayerRequest. - Client -> world -> chat (friends, ignore list, teams, general and private chat): GetFriendsList, AddFriendRequest, AddFriendResponse, RemoveFriend, GetIgnoreList, AddIgnore, RemoveIgnore, GeneralChatMessage, PrivateChatMessage, TeamInvite, TeamInviteResponse, TeamLeave, TeamKick, TeamSetLeader, TeamSetLoot, TeamGetStatus. The 77 bytes the handlers skipped are named fields now (the sender block the client fills in); the 4 unused bytes after the player ID are kept as `unknown`. - What the client receives. Chat service (ChatPackets::Client): GeneralChatMessage (replaces SendChatMessage; SendSystemMessage stays as a helper built on it), PrivateChatMessage. Client service (ClientPackets, as structs go in the file of the ServiceType in their header): SendCannedText (replaces SendMessageFail), GetFriendsListResponse, AddFriendRequest, AddFriendResponse, RemoveFriendResponse, UpdateFriendNotify, WhoResponse, ShowAllResponse, Get/Add/RemoveIgnoreResponse, TeamInvite, TeamInviteInitialResponse, and the team game messages chat writes (TeamInviteConfirm, TeamGetStatusResponse, TeamSetLeader, TeamAddPlayer, TeamRemovePlayer, TeamSetOffWorldFlag) as TeamGameMsg structs, since chat doesn't link dGame's NetGameMsg. - WORLD_ROUTE_PACKET is ChatPackets::WorldRoutePacket (its own file, dNet/WorldRoutePacket.h, since it carries packets of other services): the target and the inner packet. ChatPacketHandler::SendRouted replaces the per-function route headers and SendRoutedMsg. Dispatch: dNet/PacketDispatcher.h is a dispatch map from packet ID to (struct, handler function); packets that fail to Deserialize are logged and dropped. The chat server's switch and the world's HandlePacketChat switch are now maps. The handlers take the structs; their logic is unchanged. World code sends to chat with ChatServerLink::Send (dGame) instead of writing to Game::chatServer by hand. eChatChannel, eChatMessageResponseCode and eAddIgnoreResponse moved to dCommon/dEnums so the structs can use them. Verified: tests/dGameTests/dNetTests/Legacy/ChatPacketsLegacy.h is a verbatim copy of the old senders and readers; ChatPacketsTests (25 tests) sends a grid of inputs through both and requires identical bits, bytes and destination, checks the old readers read what the structs write, round trips every struct, checks truncated packets are refused, and has hand written golden packets for general chat, canned text, GM mute and a routed team game message. Breaking one field width (UpdateFriendNotify) and the TeamAddPlayer zone flag made the tests fail. No wire bytes changed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
390 lines
14 KiB
C++
390 lines
14 KiB
C++
/*
|
|
* Darkflame Universe
|
|
* Copyright 2018
|
|
*/
|
|
|
|
#ifndef CHATPACKETS_H
|
|
#define CHATPACKETS_H
|
|
|
|
struct SystemAddress;
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
#include "dCommonVars.h"
|
|
#include "MessageType/Chat.h"
|
|
#include "MessageType/Client.h"
|
|
#include "MessageType/Game.h"
|
|
#include "BitStreamUtils.h"
|
|
#include "eAddFriendResponseCode.h"
|
|
#include "eAddFriendResponseType.h"
|
|
#include "eAddIgnoreResponse.h"
|
|
#include "eChatChannel.h"
|
|
#include "eChatMessageResponseCode.h"
|
|
#include "eGameMasterLevel.h"
|
|
|
|
/**
|
|
* Packets of the chat service (docs/PacketArchitecture.md).
|
|
*
|
|
* Top level: packets between world servers and the chat server (MessageType::Chat). The ones a client sends go to its
|
|
* world as a ROUTE_PACKET; the world forwards them to chat with the player's object ID in front, so every such
|
|
* struct starts with `playerID`.
|
|
*
|
|
* ChatPackets::Client: the chat-service packets the client receives. What the chat server sends a client in other
|
|
* services (friends, ignore list and team responses, team game messages) is in ClientPackets.h. The chat server wraps
|
|
* them in a WorldRoutePacket (WorldRoutePacket.h) to the world the player is in, which passes the inner packet on to
|
|
* the client unchanged.
|
|
*/
|
|
namespace ChatPackets {
|
|
// World -> chat: a player finished loading into a world (also sent when they change worlds)
|
|
struct LoginSessionNotify : public LUBitStream {
|
|
static constexpr uint32_t MAX_NAME_LENGTH = 33;
|
|
|
|
LWOOBJID playerID{};
|
|
std::string playerName; // u32 length, then 1 byte per character
|
|
LWOZONEID zoneID{};
|
|
int64_t muteExpire{}; // time_t
|
|
eGameMasterLevel gmLevel{};
|
|
|
|
LoginSessionNotify() : LUBitStream(ServiceType::CHAT, MessageType::Chat::LOGIN_SESSION_NOTIFY) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
};
|
|
|
|
// World -> chat: a player left (or switched characters, or a character was deleted)
|
|
struct UnexpectedDisconnect : public LUBitStream {
|
|
LWOOBJID playerID{};
|
|
|
|
UnexpectedDisconnect() : LUBitStream(ServiceType::CHAT, MessageType::Chat::UNEXPECTED_DISCONNECT) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
};
|
|
|
|
// World -> chat: a player's GM level changed
|
|
struct GMLevelUpdate : public LUBitStream {
|
|
LWOOBJID playerID{};
|
|
eGameMasterLevel gmLevel{};
|
|
|
|
GMLevelUpdate() : LUBitStream(ServiceType::CHAT, MessageType::Chat::GMLEVEL_UPDATE) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
};
|
|
|
|
// World -> chat, and chat -> every world: a player was muted until `expire` (time_t; 1 means until reviewed)
|
|
struct GMMute : public LUBitStream {
|
|
LWOOBJID playerID{};
|
|
int64_t expire{};
|
|
|
|
GMMute() : LUBitStream(ServiceType::CHAT, MessageType::Chat::GM_MUTE) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
};
|
|
|
|
// World -> chat -> every other world: a GM's /announce
|
|
struct Announcement : public LUBitStream {
|
|
std::string title; // u32 length, then 1 byte per character
|
|
std::string message; // u32 length, then 1 byte per character
|
|
|
|
Announcement() : LUBitStream(ServiceType::CHAT, MessageType::Chat::GM_ANNOUNCE) {};
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
};
|
|
|
|
// World -> chat (custom DLU message): make a team of the players starting an activity together
|
|
struct CreateTeam : public LUBitStream {
|
|
// More can't be a team; bigger requests are dropped
|
|
static constexpr uint64_t MAX_MEMBERS = 3;
|
|
|
|
LWOOBJID leaderID{};
|
|
std::vector<LWOOBJID> members; // u64 count
|
|
LWOZONEID zoneID{};
|
|
|
|
CreateTeam() : LUBitStream(ServiceType::CHAT, MessageType::Chat::CREATE_TEAM) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
};
|
|
|
|
// Chat -> every world: a team changed (or was deleted), so worlds can share loot and credit
|
|
struct TeamUpdate : public LUBitStream {
|
|
LWOOBJID teamID{};
|
|
bool deleteTeam{}; // one bit; nothing follows when set
|
|
uint8_t lootFlag{};
|
|
std::vector<LWOOBJID> members; // u8 count
|
|
|
|
TeamUpdate() : LUBitStream(ServiceType::CHAT, MessageType::Chat::TEAM_GET_STATUS) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
};
|
|
|
|
// World -> chat -> the earner's online friends: a mission announcement (routed to the client as is)
|
|
struct AchievementNotify : public LUBitStream {
|
|
LUWString targetPlayerName{};
|
|
uint32_t missionEmailID{};
|
|
LWOOBJID earningPlayerID{};
|
|
LUWString earnerName{};
|
|
AchievementNotify() : LUBitStream(ServiceType::CHAT, MessageType::Chat::ACHIEVEMENT_NOTIFY) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
};
|
|
|
|
// World -> chat: /showall
|
|
struct ShowAllRequest : public LUBitStream {
|
|
LWOOBJID requestor = LWOOBJID_EMPTY;
|
|
bool displayZoneData = true; // one bit
|
|
bool displayIndividualPlayers = true; // one bit
|
|
|
|
ShowAllRequest() : LUBitStream(ServiceType::CHAT, MessageType::Chat::SHOW_ALL) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
};
|
|
|
|
// World -> chat: /findplayer
|
|
struct FindPlayerRequest : public LUBitStream {
|
|
LWOOBJID requestor = LWOOBJID_EMPTY;
|
|
LUWString playerName;
|
|
|
|
FindPlayerRequest() : LUBitStream(ServiceType::CHAT, MessageType::Chat::WHO) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
};
|
|
|
|
// World -> chat. A chat packet the client sent through the world server (WorldPackets::RoutePacket): its id,
|
|
// the object ID of the player who sent it (the chat server has no other way to know), then the routed bytes.
|
|
struct RoutedFromClient : public LUBitStream {
|
|
LWOOBJID senderID{};
|
|
std::vector<uint8_t> data{};
|
|
|
|
RoutedFromClient(uint8_t routedMessageID = 0) : LUBitStream(ServiceType::CHAT, routedMessageID) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
};
|
|
|
|
// Client -> world -> chat. Every packet below starts with the player's object ID (put there by the world).
|
|
// `unknown` is 4 bytes of the client's packet the server doesn't use.
|
|
|
|
// A player's friends list (sent when they load into a world)
|
|
struct GetFriendsList : public LUBitStream {
|
|
LWOOBJID playerID{};
|
|
|
|
GetFriendsList() : LUBitStream(ServiceType::CHAT, MessageType::Chat::GET_FRIENDS_LIST) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
};
|
|
|
|
struct AddFriendRequest : public LUBitStream {
|
|
LWOOBJID playerID{};
|
|
uint32_t unknown{};
|
|
LUWString friendName;
|
|
uint8_t isBestFriendRequest{};
|
|
|
|
AddFriendRequest() : LUBitStream(ServiceType::CHAT, MessageType::Chat::ADD_FRIEND_REQUEST) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
};
|
|
|
|
// The answer of the player who was asked to be friends
|
|
struct AddFriendResponse : public LUBitStream {
|
|
LWOOBJID playerID{};
|
|
uint32_t unknown{};
|
|
eAddFriendResponseCode responseCode{};
|
|
LUWString friendName;
|
|
|
|
AddFriendResponse() : LUBitStream(ServiceType::CHAT, MessageType::Chat::ADD_FRIEND_RESPONSE) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
};
|
|
|
|
struct RemoveFriend : public LUBitStream {
|
|
LWOOBJID playerID{};
|
|
uint32_t unknown{};
|
|
LUWString friendName;
|
|
|
|
RemoveFriend() : LUBitStream(ServiceType::CHAT, MessageType::Chat::REMOVE_FRIEND) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
};
|
|
|
|
struct GetIgnoreList : public LUBitStream {
|
|
LWOOBJID playerID{};
|
|
|
|
GetIgnoreList() : LUBitStream(ServiceType::CHAT, MessageType::Chat::GET_IGNORE_LIST) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
};
|
|
|
|
struct AddIgnore : public LUBitStream {
|
|
LWOOBJID playerID{};
|
|
uint32_t unknown{};
|
|
LUWString playerName;
|
|
|
|
AddIgnore() : LUBitStream(ServiceType::CHAT, MessageType::Chat::ADD_IGNORE) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
};
|
|
|
|
struct RemoveIgnore : public LUBitStream {
|
|
LWOOBJID playerID{};
|
|
uint32_t unknown{};
|
|
LUWString playerName;
|
|
|
|
RemoveIgnore() : LUBitStream(ServiceType::CHAT, MessageType::Chat::REMOVE_IGNORE) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
};
|
|
|
|
// Chat in a channel the chat server handles (team chat)
|
|
struct GeneralChatMessage : public LUBitStream {
|
|
LWOOBJID playerID{};
|
|
uint32_t unknown{};
|
|
eChatChannel chatChannel{};
|
|
uint32_t messageLength{}; // characters in message; more than MAX_MESSAGE_LENGTH is refused
|
|
// What the client fills in about itself (the server uses its own records)
|
|
LUWString senderName;
|
|
LWOOBJID senderID{};
|
|
uint16_t sourceID{};
|
|
uint8_t senderGMLevel{};
|
|
LUWString message{ 0u }; // messageLength characters
|
|
|
|
GeneralChatMessage() : LUBitStream(ServiceType::CHAT, MessageType::Chat::GENERAL_CHAT_MESSAGE) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
};
|
|
|
|
// A whisper
|
|
struct PrivateChatMessage : public LUBitStream {
|
|
LWOOBJID playerID{};
|
|
uint32_t unknown{};
|
|
eChatChannel chatChannel{};
|
|
uint32_t messageLength{}; // characters in message; more than MAX_MESSAGE_LENGTH is refused
|
|
// What the client fills in about itself (the server uses its own records)
|
|
LUWString senderName;
|
|
LWOOBJID senderID{};
|
|
uint16_t sourceID{};
|
|
uint8_t senderGMLevel{};
|
|
LUWString receiverName;
|
|
uint8_t receiverGMLevel{};
|
|
uint8_t responseCode{};
|
|
LUWString message{ 0u }; // messageLength characters
|
|
|
|
PrivateChatMessage() : LUBitStream(ServiceType::CHAT, MessageType::Chat::PRIVATE_CHAT_MESSAGE) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
};
|
|
|
|
struct TeamInvite : public LUBitStream {
|
|
LWOOBJID playerID{};
|
|
uint32_t unknown{};
|
|
LUWString invitedPlayer;
|
|
|
|
TeamInvite() : LUBitStream(ServiceType::CHAT, MessageType::Chat::TEAM_INVITE) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
};
|
|
|
|
struct TeamInviteResponse : public LUBitStream {
|
|
LWOOBJID playerID{};
|
|
uint32_t unknown{};
|
|
uint8_t declined{};
|
|
LWOOBJID leaderID{};
|
|
|
|
TeamInviteResponse() : LUBitStream(ServiceType::CHAT, MessageType::Chat::TEAM_INVITE_RESPONSE) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
};
|
|
|
|
struct TeamLeave : public LUBitStream {
|
|
LWOOBJID playerID{};
|
|
uint32_t unknown{};
|
|
|
|
TeamLeave() : LUBitStream(ServiceType::CHAT, MessageType::Chat::TEAM_LEAVE) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
};
|
|
|
|
struct TeamKick : public LUBitStream {
|
|
LWOOBJID playerID{};
|
|
uint32_t unknown{};
|
|
LUWString kickedPlayer;
|
|
|
|
TeamKick() : LUBitStream(ServiceType::CHAT, MessageType::Chat::TEAM_KICK) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
};
|
|
|
|
// Make another member the team's leader
|
|
struct TeamSetLeader : public LUBitStream {
|
|
LWOOBJID playerID{};
|
|
uint32_t unknown{};
|
|
LUWString promotedPlayer;
|
|
|
|
TeamSetLeader() : LUBitStream(ServiceType::CHAT, MessageType::Chat::TEAM_SET_LEADER) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
};
|
|
|
|
struct TeamSetLoot : public LUBitStream {
|
|
LWOOBJID playerID{};
|
|
uint32_t unknown{};
|
|
uint8_t lootFlag{};
|
|
|
|
TeamSetLoot() : LUBitStream(ServiceType::CHAT, MessageType::Chat::TEAM_SET_LOOT) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
};
|
|
|
|
// A player's team, again (sent when they load into a world)
|
|
struct TeamGetStatus : public LUBitStream {
|
|
LWOOBJID playerID{};
|
|
|
|
TeamGetStatus() : LUBitStream(ServiceType::CHAT, MessageType::Chat::TEAM_GET_STATUS) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
};
|
|
|
|
/**
|
|
* Chat-service packets the client receives, from the chat server (routed through the player's world) or from its world.
|
|
*/
|
|
namespace Client {
|
|
// Chat in a channel (world -> client; sent to everyone)
|
|
struct GeneralChatMessage : public LUBitStream {
|
|
uint64_t unknown{}; // always 0 from the server
|
|
uint8_t chatChannel{};
|
|
LUWString senderName;
|
|
LWOOBJID senderID{};
|
|
uint16_t sourceID{};
|
|
uint8_t senderGMLevel{}; // DLU always sends 0
|
|
std::u16string message; // u32 length (without the terminator), the characters, then a 0 terminator
|
|
|
|
GeneralChatMessage() : LUBitStream(ServiceType::CHAT, MessageType::Chat::GENERAL_CHAT_MESSAGE) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
};
|
|
|
|
// A whisper, or team chat, or why a whisper didn't go out
|
|
struct PrivateChatMessage : public LUBitStream {
|
|
LWOOBJID playerID{}; // the sender again
|
|
eChatChannel chatChannel{};
|
|
uint32_t messageLength{}; // DLU always sends 0
|
|
LUWString senderName;
|
|
LWOOBJID senderID{};
|
|
uint16_t sourceID{};
|
|
eGameMasterLevel senderGMLevel{};
|
|
LUWString receiverName;
|
|
eGameMasterLevel receiverGMLevel{};
|
|
eChatMessageResponseCode responseCode{};
|
|
// Everything that is left. The sender's message as it came in, with its padding.
|
|
LUWString message{ 0u };
|
|
|
|
PrivateChatMessage() : LUBitStream(ServiceType::CHAT, MessageType::Chat::PRIVATE_CHAT_MESSAGE) {}
|
|
void Serialize(RakNet::BitStream& bitStream) const override;
|
|
bool Deserialize(RakNet::BitStream& bitStream) override;
|
|
};
|
|
}
|
|
|
|
// A system message (chat channel 4) to one client, or everyone when sysAddr is UNASSIGNED_SYSTEM_ADDRESS.
|
|
// `broadcast` is not used: a message with an address only goes to that client.
|
|
void SendSystemMessage(const SystemAddress& sysAddr, const std::string& message, bool broadcast = false);
|
|
void SendSystemMessage(const SystemAddress& sysAddr, const std::u16string& message, bool broadcast = false);
|
|
};
|
|
|
|
#endif // CHATPACKETS_H
|