mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 19:03:43 +00:00
With new binaries in place, master moves everything onto them while the server keeps running (the dashboard's Live update, /liveupdate or SIGUSR2 to master): - database migrations of the new build first; a failure stops there - UGC finishes the jobs it is running (queued rows stay pending), auth restarts, chat hands its teams to master for the next chat server; master starts the new processes and retries ones that don't come back - once the new chat server is up (CHAT_SERVER_READY) every world connects at once and sends its players again (LoginSessionNotify resync, no login logged) - every world instance is replaced with an instance migration: public worlds and private ones (same password) at once, properties after the old instance saved and froze the property (MIGRATE_PREPARE: no building, claiming or saving there any more), activity zones and character select once their players left or after a wait; empty instances just stop, zones in prestart_worlds get a new one first - the dashboard restarts last and picks the status up again Players land where they stood (position carried in CarriedPlayerState, also on properties and Moon Base). Draining instances get no new players (InstanceMigration::AcceptsNewPlayers) and show as "Moving players" in the world list. The order lives in LiveUpdateMachine.h without master state and is unit tested; master's glue is LiveUpdateCoordinator. Master itself is not replaced. Message IDs are appended only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
402 lines
15 KiB
C++
402 lines
15 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;
|
|
};
|
|
|
|
/**
|
|
* 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
|