feat(net): WORLD_FILES, WORLD_RELOAD and WORLD_FILES_STATUS master messages

Appended at the end of MessageType::Master (47-49); the pin test is
updated. A world sends its zone file list to master once it is ready.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-09-30 02:44:16 -05:00
parent e709855b7d
commit ab395a1af9
5 changed files with 319 additions and 1 deletions

View File

@@ -1763,7 +1763,7 @@ static_assert(static_cast<int64_t>(MessageType::Game::CAN_ITEMS_BE_REFORGED) ==
static_assert(static_cast<int64_t>(MessageType::Game::NOTIFY_CLIENT_RAIL_START_FAILED) == 1771);
static_assert(static_cast<int64_t>(MessageType::Game::GET_IS_ON_RAIL) == 1772);
// MessageType::Master: 44 enumerators
// MessageType::Master: 47 enumerators
static_assert(static_cast<int64_t>(MessageType::Master::REQUEST_ZONE_TRANSFER) == 1);
static_assert(static_cast<int64_t>(MessageType::Master::REQUEST_ZONE_TRANSFER_RESPONSE) == 2);
static_assert(static_cast<int64_t>(MessageType::Master::SERVER_INFO) == 3);
@@ -1810,6 +1810,9 @@ static_assert(static_cast<int64_t>(MessageType::Master::CHAT_SERVER_READY) == 43
static_assert(static_cast<int64_t>(MessageType::Master::PROFILE_REQUEST) == 44);
static_assert(static_cast<int64_t>(MessageType::Master::PROFILE_RESULT) == 45);
static_assert(static_cast<int64_t>(MessageType::Master::CDCLIENT_RELOAD) == 46);
static_assert(static_cast<int64_t>(MessageType::Master::WORLD_FILES) == 47);
static_assert(static_cast<int64_t>(MessageType::Master::WORLD_RELOAD) == 48);
static_assert(static_cast<int64_t>(MessageType::Master::WORLD_FILES_STATUS) == 49);
// MessageType::Server: 3 enumerators
static_assert(static_cast<int64_t>(MessageType::Server::VERSION_CONFIRM) == 0);

View File

@@ -3,6 +3,7 @@
#include "master/DashboardMessages.h"
#include "master/DataChanged.h"
#include "master/InstanceMigration.h"
#include "master/WorldFiles.h"
#include "master/MessageCapture.h"
#include "master/PlayerAction.h"
#include "PacketDispatcher.h"
@@ -572,3 +573,89 @@ TEST(MasterPacketsTests, CDClientReload) {
EXPECT_FALSE(read.Deserialize(stream)) << fdb;
}
}
TEST(MasterPacketsTests, WorldFilesReport) {
WorldFilesReport report;
report.zoneId = 1200;
report.instanceId = 3;
report.cloneId = 0;
report.files.push_back({ ZoneFileLog::eKind::ZONE, false, 1234, 0x0123456789abcdefULL, "/srv/res/maps/nimbusstation/nd_nimbus_station.luz" });
report.files.push_back({ ZoneFileLog::eKind::SCENE, true, 99, 7, "maps/nimbusstation/nd_scene.lvl" });
report.files.push_back({ ZoneFileLog::eKind::NAVMESH, false, 1ULL << 33, 0xffffffffffffffffULL, "C:/server/navmeshes/1200.bin" });
ExpectHeaderThenSerialize(report, MessageType::Master::WORLD_FILES);
RakNet::BitStream stream;
report.Serialize(stream);
WorldFilesReport read;
ASSERT_TRUE(read.Deserialize(stream));
EXPECT_EQ(read.zoneId, 1200u);
EXPECT_EQ(read.instanceId, 3u);
EXPECT_EQ(read.files, report.files);
// An empty path or an unknown kind is refused
for (const auto& bad : { ZoneFileLog::Entry{ ZoneFileLog::eKind::ZONE, false, 1, 1, "" },
ZoneFileLog::Entry{ static_cast<ZoneFileLog::eKind>(200), false, 1, 1, "x.luz" } }) {
WorldFilesReport badReport;
badReport.files.push_back(bad);
RakNet::BitStream badStream;
badReport.Serialize(badStream);
WorldFilesReport badRead;
EXPECT_FALSE(badRead.Deserialize(badStream));
}
RakNet::BitStream truncated;
report.Serialize(truncated);
RakNet::BitStream cut(truncated.GetData(), truncated.GetNumberOfBytesUsed() - 3, false);
WorldFilesReport cutRead;
EXPECT_FALSE(cutRead.Deserialize(cut));
}
TEST(MasterPacketsTests, WorldReloadRequest) {
WorldReloadRequest request;
request.zoneId = 1100;
request.warnSeconds = 0;
request.requesterId = 42;
request.requestedBy = "a GM";
ExpectHeaderThenSerialize(request, MessageType::Master::WORLD_RELOAD);
RakNet::BitStream stream;
request.Serialize(stream);
WorldReloadRequest read;
ASSERT_TRUE(read.Deserialize(stream));
EXPECT_EQ(read.zoneId, 1100u);
EXPECT_EQ(read.warnSeconds, 0);
EXPECT_EQ(read.requesterId, 42);
EXPECT_EQ(read.requestedBy, "a GM");
}
TEST(MasterPacketsTests, WorldFilesStatus) {
WorldFilesStatus status;
status.watching = true;
status.watchSeconds = 5;
status.seamless = false;
WorldFilesStatus::Zone zone;
zone.zoneId = 1100;
WorldFilesStatus::File file;
file.disk = { ZoneFileLog::eKind::TERRAIN, false, 5, 6, "/srv/res/maps/x.raw" };
file.hashed = true;
file.changed = true;
zone.files.push_back(file);
file.disk.path = "/srv/res/maps/y.raw";
file.missing = true;
file.changed = false;
zone.files.push_back(file);
zone.instances.push_back({ 1, 0, 12, true, false });
zone.instances.push_back({ 2, 0, 0, false, true });
zone.message = "Reloading 1 instance(s)";
status.zones.push_back(zone);
status.zones.push_back({});
ExpectHeaderThenSerialize(status, MessageType::Master::WORLD_FILES_STATUS);
RakNet::BitStream stream;
status.Serialize(stream);
WorldFilesStatus read;
ASSERT_TRUE(read.Deserialize(stream));
EXPECT_TRUE(read.watching);
EXPECT_EQ(read.watchSeconds, 5);
EXPECT_EQ(read.zones, status.zones);
}