mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-04 11:53:44 +00:00
Every MASTER service packet is now an LUBitStream struct (docs/PacketArchitecture.md, PR 14) and the master server's switch is a dispatch map (PacketDispatcher), as are the master handlers of the world, chat and dashboard servers. - dNet/MasterPackets.h: RequestZoneTransfer, RequestZoneTransferResponse, ServerInfo, RequestSessionKey, SetSessionKey, SessionKeyResponse, NewSessionAlert, PlayerAdded / PlayerRemoved, CreatePrivateZone, RequestPrivateZone (passwords still cut to 50 characters when read), WorldReady, WorldReadyInfo (WORLD_READY to the dashboard), PrepZone, Shutdown, ShutdownResponse, WorldShutDown (SHUTDOWN_RESPONSE to the dashboard), ShutdownUniverse, AffirmTransferRequest/Response, RequestServerList, ServerListResponse, DashboardShutdown, ConfigReload, InstanceShutdown. The Send* functions are gone; MasterPackets::SendToMaster(msg) and SendTo(sysAddr, msg) send a struct. - The dashboard and instance migration structs (PlayerAction, DataChanged, Dashboard messages, MessageCapture, InstanceMigration) are LUBitStreams of the MASTER service now and moved to dNet/master/, included by MasterPackets.h. Their payloads are unchanged; master forwards them by re-serializing the struct instead of copying raw bytes. - InstanceManager, ZoneInstanceManager, MigrationCoordinator, dServer (server info, zone transfer response), auth (SET_SESSION_KEY), the world (session keys, player added and removed, world ready, shutdown response, affirmations, prep zone, shutdown universe) and the dashboard (server list, instance shutdown, config reload, announcements, player actions, message capture) send and read structs. - The login stamps are a `stamps` field of RequestZoneTransfer and RequestZoneTransferResponse (read leniently as before: a message without them reads as empty); master adds its stamps in the REQUEST_ZONE_TRANSFER handler and when it answers, as it did. - InstanceManager::GetInstanceBySysAddr takes a const address. Verified: tests/dGameTests/dNetTests/Legacy/MasterPacketsLegacy.h is a verbatim copy of the old writers and readers; MasterPacketsTests requires identical bytes for a grid of inputs, checks the old readers read what the structs write, round trips and truncation, hand written golden packets, that zone transfers without stamps still read, that the dashboard/migration structs write what "header + Serialize" wrote, and that the dispatcher drops truncated packets. No wire bytes changed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
129 lines
5.6 KiB
C++
129 lines
5.6 KiB
C++
#ifndef __PLAYERACTION__H__
|
|
#define __PLAYERACTION__H__
|
|
|
|
#include <algorithm>
|
|
#include <cstdint>
|
|
#include <string>
|
|
|
|
#include "BitStream.h"
|
|
#include "BitStreamUtils.h"
|
|
#include "MessageType/Master.h"
|
|
#include "dCommonVars.h"
|
|
|
|
/**
|
|
* Actions the dashboard asks world servers to apply to players who are currently online.
|
|
* The dashboard sends MessageType::Master::PLAYER_ACTION to master, which forwards it to every world.
|
|
* Each world answers PLAYER_ACTION_RESULT, and master sends the dashboard a single aggregated result
|
|
* once every world has answered or the request timed out.
|
|
*/
|
|
enum class ePlayerAction : uint8_t {
|
|
KICK_ACCOUNT, // Disconnect every session of accountId
|
|
REFRESH_ACCOUNT, // Reload account data (GM level) for online sessions of accountId
|
|
REFRESH_CHARACTER, // Reload character data (restrictions) for characterId if loaded
|
|
RESCUE_CHARACTER, // Transfer characterId to zoneId if it is loaded in a world, landing on spawn point `text` (empty: the zone's default)
|
|
// Tell a player a moderator decided on something they asked for (approved = yes/no, text = name or reason)
|
|
NAME_MODERATED, // characterId's requested name; text is the name
|
|
PET_NAME_MODERATED, // pet targetId's name; text is the name
|
|
PROPERTY_MODERATED, // property targetId owned by characterId; text is the rejection reason
|
|
TELEPORT_TO_PLAYER, // move characterId to where character targetId stands (only when both are in the same world)
|
|
WARN_ACCOUNT, // show accountId's online character a moderator's warning (text)
|
|
RELOAD_VANITY, // respawn the vanity NPCs from the vanity/*.xml files (every world; answers 1 per world reloaded)
|
|
CHAT_MESSAGE, // show `text` in chat from `name` (e.g. "[Discord] Bob") in every world, or only zoneId; answers players reached
|
|
RELOAD_CHAT_FILTER, // load the chat filter's words added on the dashboard again (every world, answering 1 each; master also passes it to the chat server)
|
|
// Change mission targetId of characterId if it is loaded in a world, as the GM commands do
|
|
MISSION_COMPLETE, // as /completemission; approved = give its rewards
|
|
MISSION_RESET, // as /resetmission
|
|
MISSION_ACCEPT, // as /addmission (prerequisites are skipped)
|
|
RELOAD_LIVE_OPS, // load the running live events and open challenges again (every world, answering 1 each; see LiveEvents.h)
|
|
};
|
|
|
|
struct PlayerActionRequest : public LUBitStream {
|
|
PlayerActionRequest() : LUBitStream(ServiceType::MASTER, MessageType::Master::PLAYER_ACTION) {}
|
|
|
|
uint32_t requestId{};
|
|
ePlayerAction action{};
|
|
uint32_t accountId{};
|
|
LWOOBJID characterId{};
|
|
LWOMAPID zoneId{};
|
|
// eServerDisconnectIdentifiers shown to the client for KICK_ACCOUNT
|
|
uint32_t disconnectReason{};
|
|
// For the *_MODERATED actions
|
|
LWOOBJID targetId{};
|
|
bool approved{};
|
|
std::string text;
|
|
std::string name; // CHAT_MESSAGE: who it's from
|
|
int32_t instanceId{ -1 }; // CHAT_MESSAGE: only this instance of zoneId (-1: every instance)
|
|
|
|
static constexpr uint16_t MAX_TEXT = 300;
|
|
|
|
// How many bytes of UTF-8 text fit in maxBytes without cutting a character in half
|
|
static size_t Utf8Length(const std::string& value, size_t maxBytes) {
|
|
if (value.size() <= maxBytes) return value.size();
|
|
size_t length = maxBytes;
|
|
// Back up over continuation bytes (10xxxxxx) to the start of the character that doesn't fit
|
|
while (length > 0 && (static_cast<unsigned char>(value[length]) & 0xC0) == 0x80) length--;
|
|
return length;
|
|
}
|
|
|
|
void Serialize(RakNet::BitStream& bitStream) const override {
|
|
bitStream.Write(requestId);
|
|
bitStream.Write(action);
|
|
bitStream.Write(accountId);
|
|
bitStream.Write(characterId);
|
|
bitStream.Write(zoneId);
|
|
bitStream.Write(disconnectReason);
|
|
bitStream.Write(targetId);
|
|
bitStream.Write<uint8_t>(approved);
|
|
const auto length = static_cast<uint16_t>(Utf8Length(text, MAX_TEXT));
|
|
bitStream.Write(length);
|
|
bitStream.Write(text.data(), length);
|
|
const auto nameLength = static_cast<uint16_t>(Utf8Length(name, MAX_TEXT));
|
|
bitStream.Write(nameLength);
|
|
bitStream.Write(name.data(), nameLength);
|
|
bitStream.Write(instanceId);
|
|
}
|
|
|
|
bool Deserialize(RakNet::BitStream& bitStream) override {
|
|
uint8_t approvedByte{};
|
|
uint16_t length{};
|
|
if (!(bitStream.Read(requestId) && bitStream.Read(action) && bitStream.Read(accountId) && bitStream.Read(characterId) &&
|
|
bitStream.Read(zoneId) && bitStream.Read(disconnectReason) && bitStream.Read(targetId) && bitStream.Read(approvedByte) &&
|
|
bitStream.Read(length)) || length > MAX_TEXT) return false;
|
|
approved = approvedByte != 0;
|
|
text.resize(length);
|
|
if (length != 0 && !bitStream.Read(text.data(), length)) return false;
|
|
uint16_t nameLength{};
|
|
if (!bitStream.Read(nameLength) || nameLength > MAX_TEXT) return false;
|
|
name.resize(nameLength);
|
|
if (nameLength != 0 && !bitStream.Read(name.data(), nameLength)) return false;
|
|
return bitStream.Read(instanceId);
|
|
}
|
|
};
|
|
|
|
struct PlayerActionResult : public LUBitStream {
|
|
PlayerActionResult() : LUBitStream(ServiceType::MASTER, MessageType::Master::PLAYER_ACTION_RESULT) {}
|
|
|
|
uint32_t requestId{};
|
|
ePlayerAction action{};
|
|
// Number of sessions/characters the action applied to. Zero means the player was not online.
|
|
uint32_t affected{};
|
|
// Set by master when not every world answered before the timeout
|
|
bool timedOut{};
|
|
|
|
void Serialize(RakNet::BitStream& bitStream) const override {
|
|
bitStream.Write(requestId);
|
|
bitStream.Write(action);
|
|
bitStream.Write(affected);
|
|
bitStream.Write<uint8_t>(timedOut);
|
|
}
|
|
|
|
bool Deserialize(RakNet::BitStream& bitStream) override {
|
|
uint8_t timedOutByte{};
|
|
const bool ok = bitStream.Read(requestId) && bitStream.Read(action) && bitStream.Read(affected) && bitStream.Read(timedOutByte);
|
|
timedOut = timedOutByte != 0;
|
|
return ok;
|
|
}
|
|
};
|
|
|
|
#endif //!__PLAYERACTION__H__
|