mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-03 03:13:50 +00:00
feat(net): CaptureTools::Worlds lists each captured character's world visits
One entry each time the packets a character's client sends carry another zone or instance, in time order, for following them across world servers in playback. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -151,6 +151,21 @@ namespace CaptureTools {
|
||||
return tracks;
|
||||
}
|
||||
|
||||
std::vector<WorldVisit> Worlds(const std::vector<Record>& records, int64_t startUs) {
|
||||
std::vector<WorldVisit> visits;
|
||||
std::map<LWOOBJID, size_t> last; // character -> their latest entry in visits
|
||||
for (const auto& record : records) {
|
||||
const auto& h = record.header;
|
||||
// Only what the client sent: the old world can still send a few packets after the client reached the new one
|
||||
if (h.source != static_cast<uint8_t>(eCaptureSource::WORLD) || !h.characterId || (h.flags & PacketRecordFlags::GAP) || !FromClient(h)) continue;
|
||||
const auto it = last.find(h.characterId);
|
||||
if (it != last.end() && visits[it->second].zoneId == h.zoneId && visits[it->second].instanceId == h.instanceId) continue;
|
||||
last[h.characterId] = visits.size();
|
||||
visits.push_back({ h.characterId, static_cast<float>(h.timeUs - startUs) / 1e6f, h.zoneId, h.instanceId, h.cloneId });
|
||||
}
|
||||
return visits;
|
||||
}
|
||||
|
||||
std::map<std::string, LWOOBJID> MakePortable(CaptureBundle::Bundle& bundle) {
|
||||
std::map<LWOOBJID, std::string> characters;
|
||||
std::map<uint32_t, uint32_t> accounts;
|
||||
|
||||
@@ -35,6 +35,17 @@ namespace CaptureTools {
|
||||
// Where each captured character moved (their POSITION_UPDATEs), per zone and instance
|
||||
std::vector<Track> Tracks(const std::vector<CaptureBundle::Record>& records, int64_t startUs);
|
||||
|
||||
struct WorldVisit {
|
||||
LWOOBJID characterId{};
|
||||
float t{}; // seconds from the capture's start: the character's first packet on this world server
|
||||
uint32_t zoneId{};
|
||||
uint32_t instanceId{};
|
||||
uint32_t cloneId{};
|
||||
};
|
||||
// Which world server each captured character was on, in time order: one entry each time the packets their client
|
||||
// sends move to another zone or instance (zone 0 is character select)
|
||||
std::vector<WorldVisit> Worlds(const std::vector<CaptureBundle::Record>& records, int64_t startUs);
|
||||
|
||||
/**
|
||||
* Makes a bundle portable: the source server's character and account IDs are replaced by placeholders
|
||||
* (PLACEHOLDER_BASE + n, written in the records' bytes and headers), listed in meta.ids as "char#n" / "account#n";
|
||||
|
||||
@@ -416,6 +416,39 @@ TEST(CaptureToolsTests, TimelineTracksAndBundles) {
|
||||
std::filesystem::remove(path);
|
||||
}
|
||||
|
||||
TEST(CaptureToolsTests, WorldsFollowTheClientAcrossWorldServers) {
|
||||
constexpr LWOOBJID character = 1152921510436607007LL;
|
||||
WorldPackets::PositionUpdate move;
|
||||
const auto at = [&](int64_t timeUs, uint32_t zone, uint32_t instance, ePacketDirection direction = ePacketDirection::RECEIVED) {
|
||||
auto record = MakeRecord(move, timeUs, eCaptureSource::WORLD, direction, character);
|
||||
record.header.zoneId = zone;
|
||||
record.header.instanceId = instance;
|
||||
record.header.cloneId = zone == 1200 ? 0 : 7;
|
||||
return record;
|
||||
};
|
||||
std::vector<CaptureBundle::Record> records{
|
||||
at(1000000, 0, 1), // character select
|
||||
at(2000000, 1000, 2), at(2500000, 1000, 2), // the first world
|
||||
at(4000000, 1100, 3), // transferred to another zone
|
||||
at(4100000, 1000, 2, ePacketDirection::SENT), // the old world still sending: not a move
|
||||
at(5000000, 1100, 3),
|
||||
at(6000000, 1100, 4), // another instance of the same zone
|
||||
MakeRecord(move, 7000000, eCaptureSource::WORLD, ePacketDirection::RECEIVED, 0) // nobody's
|
||||
};
|
||||
const auto worlds = CaptureTools::Worlds(records, 1000000);
|
||||
ASSERT_EQ(worlds.size(), 4u);
|
||||
EXPECT_EQ(worlds[0].zoneId, 0u);
|
||||
EXPECT_FLOAT_EQ(worlds[0].t, 0.0f);
|
||||
EXPECT_EQ(worlds[1].zoneId, 1000u);
|
||||
EXPECT_FLOAT_EQ(worlds[1].t, 1.0f);
|
||||
EXPECT_EQ(worlds[2].zoneId, 1100u);
|
||||
EXPECT_EQ(worlds[2].instanceId, 3u);
|
||||
EXPECT_EQ(worlds[2].cloneId, 7u);
|
||||
EXPECT_FLOAT_EQ(worlds[2].t, 3.0f);
|
||||
EXPECT_EQ(worlds[3].instanceId, 4u);
|
||||
EXPECT_EQ(worlds[3].characterId, character);
|
||||
}
|
||||
|
||||
TEST(CaptureToolsTests, AnonymiseKeepsSizes) {
|
||||
WorldPackets::GeneralChatMessage chat;
|
||||
chat.message = u"my secret plans";
|
||||
|
||||
Reference in New Issue
Block a user