Files
DarkflameServer/dNet/ChatPackets.h
Aaron Kimbrell 130f3566cc wire: guild packets and DisplayGuildCreateBox as structs
The guild packets of the 1.10.64 client (docs/Guilds.md) as Serialize/Deserialize structs, laid out the way the client's
packet handlers read them: GUILD_CREATE_RESPONSE, GUILD_INVITE, GUILD_INVITE_INITIAL_RESPONSE, _FINAL_RESPONSE,
_CONFIRM, GUILD_ADD_PLAYER, GUILD_REMOVE_PLAYER, GUILD_LOGIN_LOGOUT and GUILD_DATA (ClientPackets), the world packet
TMP_GUILD_CREATE the create box sends, the chat packets the client sends through its world (GUILD_INVITE,
GUILD_INVITE_RESPONSE, GUILD_LEAVE, GUILD_GET_ALL), DLU's world <-> chat packets (GUILD_CREATE, GUILD_KICK,
GUILD_GET_STATUS as the chat server's guild update to a world, and GUILD_SET_RANK and GUILD_DISBAND appended to
MessageType::Chat after CREATE_TEAM) and the game message DisplayGuildCreateBox (626). Fixed-size names always keep a
NUL, since the client reads them as C strings. Enums eGuildCreateResponse, eGuildInviteResponse,
eGuildInviteFinalResponse, eGuildRank and eGuildLeaveReason. Nothing sends them yet.

Check: GuildPacketsTests compares every packet with the bytes at the client's offsets. Nothing to check in game.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 06:48:40 -05:00

523 lines
20 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{};
// Sent again to a new chat server after a live update restarted chat (CHAT_SERVER_READY): the player didn't
// log in or change zones. Written last, only when set, and read only when there.
bool resync{};
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 -> 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
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;
};
// The lowest chat mode among who would read the player's team chat (chatChannel 8 team, 10 local team)
struct RequestMinimumChatMode : public LUBitStream {
LWOOBJID playerID{};
uint32_t unknown{};
uint8_t chatChannel{};
RequestMinimumChatMode() : LUBitStream(ServiceType::CHAT, MessageType::Chat::REQUEST_MINIMUM_CHAT_MODE) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
};
// The same for a private message to recipientName (chatChannel 7)
struct RequestMinimumChatModePrivate : public LUBitStream {
LWOOBJID playerID{};
uint32_t unknown{};
uint8_t chatChannel{};
LUWString recipientName;
RequestMinimumChatModePrivate() : LUBitStream(ServiceType::CHAT, MessageType::Chat::REQUEST_MINIMUM_CHAT_MODE_PRIVATE) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
};
// Guilds (docs/Guilds.md). The client sends GUILD_INVITE, GUILD_INVITE_RESPONSE, GUILD_LEAVE and GUILD_GET_ALL
// (routed like the ones above); the others are DLU's world <-> chat packets.
// Invite a player (by name) to the sender's guild
struct GuildInvite : public LUBitStream {
LWOOBJID playerID{};
uint32_t unknown{};
LUWString invitedPlayer{ 33 };
GuildInvite() : LUBitStream(ServiceType::CHAT, MessageType::Chat::GUILD_INVITE) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
};
// The answer to the invite in the message box (button 1 accepts: declined 0)
struct GuildInviteResponse : public LUBitStream {
LWOOBJID playerID{};
uint32_t unknown{};
uint8_t declined{};
GuildInviteResponse() : LUBitStream(ServiceType::CHAT, MessageType::Chat::GUILD_INVITE_RESPONSE) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
};
// Leave the guild. The client sends 66 more bytes of whatever was on its stack; they are not read.
struct GuildLeave : public LUBitStream {
LWOOBJID playerID{};
uint32_t unknown{};
GuildLeave() : LUBitStream(ServiceType::CHAT, MessageType::Chat::GUILD_LEAVE) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
};
// The player's guild and its members (answered with ClientPackets::GuildData)
struct GuildGetAll : public LUBitStream {
LWOOBJID playerID{};
uint32_t unknown{};
GuildGetAll() : LUBitStream(ServiceType::CHAT, MessageType::Chat::GUILD_GET_ALL) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
};
// World -> chat: the name from the client's TMP_GUILD_CREATE
struct GuildCreate : public LUBitStream {
LWOOBJID playerID{};
LUWString guildName{ 31 };
GuildCreate() : LUBitStream(ServiceType::CHAT, MessageType::Chat::GUILD_CREATE) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
};
// World -> chat: /gkick (the client never sends GUILD_KICK; the layout is the team kick's)
struct GuildKick : public LUBitStream {
LWOOBJID playerID{};
uint32_t unknown{};
LUWString kickedPlayer{ 33 };
GuildKick() : LUBitStream(ServiceType::CHAT, MessageType::Chat::GUILD_KICK) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
};
// World -> chat: change a member's rank (eGuildRank; LEADER hands the guild over)
struct GuildSetRank : public LUBitStream {
LWOOBJID playerID{};
LUWString targetPlayer{ 33 };
uint8_t rank{};
GuildSetRank() : LUBitStream(ServiceType::CHAT, MessageType::Chat::GUILD_SET_RANK) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
};
// World -> chat: the leader disbands the guild
struct GuildDisband : public LUBitStream {
LWOOBJID playerID{};
GuildDisband() : LUBitStream(ServiceType::CHAT, MessageType::Chat::GUILD_DISBAND) {}
void Serialize(RakNet::BitStream& bitStream) const override;
bool Deserialize(RakNet::BitStream& bitStream) override;
};
// Chat -> the character's world: its guild changed; the world puts it in the character component (guild 0: none).
// guildName is what other players may see (empty while the name waits for moderation).
struct GuildStatus : public LUBitStream {
LWOOBJID characterID{};
LWOOBJID guildID{};
LUWString guildName{ 31 };
GuildStatus() : LUBitStream(ServiceType::CHAT, MessageType::Chat::GUILD_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