fix: keep the zone boundary lines and read the luz scene count as the client does

Each boundary line's destination is read as a map ID and an instance ID into a LWOZONEID. The scene count is a byte before version 37 and a u32 from it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-10-26 20:33:41 -05:00
parent b98be3695c
commit f8a99c45a5
4 changed files with 83 additions and 21 deletions

View File

@@ -132,16 +132,20 @@ namespace TerrainTestWriter {
NiPoint3 position;
};
// A Latest (41) version .luz with no paths
// A .luz (version 36 to 41) with no paths
inline std::string WriteLuz(uint32_t worldID, const std::vector<TestScene>& scenes, uint8_t boundaryCount, const std::string& rawName,
const std::vector<std::pair<TestTransitionPoint, TestTransitionPoint>>& transitions) {
const std::vector<std::pair<TestTransitionPoint, TestTransitionPoint>>& transitions, uint32_t version = 41) {
std::ostringstream stream;
Write<uint32_t>(stream, 41); // version
Write(stream, version);
Write<uint32_t>(stream, 5); // revision
Write(stream, worldID);
Write(stream, NiPoint3(1.0f, 2.0f, 3.0f)); // spawn position
for (const float value : { 1.0f, 0.0f, 0.0f, 0.0f }) Write(stream, value); // spawn rotation
Write<uint32_t>(stream, static_cast<uint32_t>(scenes.size()));
if (version >= 38) {
Write(stream, NiPoint3(1.0f, 2.0f, 3.0f)); // spawn position
for (const float value : { 1.0f, 0.0f, 0.0f, 0.0f }) Write(stream, value); // spawn rotation
}
// the scene count is a byte before version 37
if (version >= 37) Write<uint32_t>(stream, static_cast<uint32_t>(scenes.size()));
else Write<uint8_t>(stream, static_cast<uint8_t>(scenes.size()));
for (const auto& scene : scenes) {
WriteString8(stream, scene.filename);
Write(stream, scene.id);
@@ -157,7 +161,8 @@ namespace TerrainTestWriter {
for (uint8_t i = 0; i < boundaryCount; ++i) {
Write(stream, NiPoint3(0.0f, 0.0f, 1.0f));
Write(stream, NiPoint3(10.0f, 0.0f, static_cast<float>(i)));
Write<uint32_t>(stream, 1100);
Write<uint16_t>(stream, 1100); // map
Write<uint16_t>(stream, i + 1); // instance
Write<uint32_t>(stream, 2);
Write(stream, NiPoint3(5.0f, 6.0f, 7.0f));
}
@@ -168,7 +173,15 @@ namespace TerrainTestWriter {
Write<uint32_t>(stream, static_cast<uint32_t>(transitions.size()));
for (const auto& [from, to] : transitions) {
for (const auto& point : { from, to }) {
// before version 40 a transition has a name and a width
if (version < 40) {
WriteString8(stream, "transition");
Write(stream, 2.5f);
}
// five points from version 34 to 38 (the last three repeat the second here), two otherwise
const size_t pointCount = version >= 34 && version < 39 ? 5 : 2;
for (size_t i = 0; i < pointCount; ++i) {
const auto& point = i == 0 ? from : to;
Write(stream, point.sceneID);
Write(stream, point.layerID);
Write(stream, point.position);

View File

@@ -54,7 +54,7 @@ protected:
}
// Scenes 0 (global), 1, 2 and 3 (general) and 5 (audio, layer 1); transitions 1 <-> 2 and 2 <-> 3
void WriteZone(const std::string& rawName = "test.raw", uint8_t boundaryCount = 2) const {
void WriteZone(const std::string& rawName = "test.raw", uint8_t boundaryCount = 2, uint32_t version = 41) const {
const std::vector<TestScene> scenes = {
{ "global.lvl", 0, 0, "Global", 10, 20, 30 },
{ "one.lvl", 1, 0, "Scene One", 255, 0, 0 },
@@ -65,7 +65,7 @@ protected:
WriteFile("test.luz", WriteLuz(TEST_MAP, scenes, boundaryCount, rawName, {
{ { 1, 0, NiPoint3(1.0f, 0.0f, 0.0f) }, { 2, 0, NiPoint3(2.0f, 0.0f, 0.0f) } },
{ { 2, 0, NiPoint3(3.0f, 0.0f, 0.0f) }, { 3, 0, NiPoint3(4.0f, 0.0f, 0.0f) } },
}));
}, version));
}
// One 3x3 point chunk from (0, 0) to (4, 4) whose 2x2 scene map holds scenes 1 2 / 3 1
@@ -99,9 +99,20 @@ TEST_F(ZoneSceneTest, ReadsScenesBoundariesAndTransitions) {
EXPECT_EQ(zone->GetWorldID(), TEST_MAP);
EXPECT_EQ(zone->GetZoneRawPath(), "test.raw");
EXPECT_EQ(zone->GetZoneName(), "Test Zone"); // read after the skipped boundary lines
EXPECT_EQ(zone->GetZoneName(), "Test Zone"); // read after the boundary lines
EXPECT_EQ(zone->GetZonePath(), "maps/testzone/");
const auto& boundaries = zone->GetBoundaries();
ASSERT_EQ(boundaries.size(), 2);
EXPECT_FLOAT_EQ(boundaries[1].normal.z, 1.0f);
EXPECT_FLOAT_EQ(boundaries[1].point.x, 10.0f);
EXPECT_FLOAT_EQ(boundaries[1].point.z, 1.0f);
EXPECT_EQ(boundaries[1].destinationZone.GetMapID(), 1100);
EXPECT_EQ(boundaries[1].destinationZone.GetInstanceID(), 2);
EXPECT_EQ(boundaries[1].destinationZone.GetCloneID(), 0);
EXPECT_EQ(boundaries[1].destinationSceneID, 2);
EXPECT_FLOAT_EQ(boundaries[1].spawnLocation.y, 6.0f);
ASSERT_EQ(zone->GetScenes().size(), 5);
const SceneRef* one = zone->GetScene(LWOSCENEID(1, 0));
ASSERT_NE(one, nullptr);
@@ -132,9 +143,32 @@ TEST_F(ZoneSceneTest, ZoneWithoutBoundaryLines) {
WriteFile("test.raw", MakeRaw());
const Zone* zone = LoadZone();
EXPECT_EQ(zone->GetZoneName(), "Test Zone");
EXPECT_TRUE(zone->GetBoundaries().empty());
EXPECT_EQ(zone->GetSceneTransitions().size(), 2);
}
// Version 37 has a u32 scene count (a byte before it) and no spawn point; 36 and 37 have named five point transitions
TEST_F(ZoneSceneTest, ReadsOlderZoneVersions) {
for (const uint32_t version : { 36u, 37u, 38u, 39u, 40u }) {
WriteZone("test.raw", 2, version);
WriteFile("test.raw", MakeRaw());
const Zone* zone = LoadZone();
EXPECT_EQ(zone->GetScenes().size(), 5) << version;
EXPECT_EQ(zone->GetBoundaries().size(), 2) << version;
EXPECT_EQ(zone->GetZoneRawPath(), "test.raw") << version;
EXPECT_EQ(zone->GetZoneName(), "Test Zone") << version;
ASSERT_EQ(zone->GetSceneTransitions().size(), 2) << version;
const auto& transition = zone->GetSceneTransitions()[1];
EXPECT_EQ(transition.points.size(), version < 39 ? 5 : 2) << version;
EXPECT_EQ(transition.points[1].sceneID, LWOSCENEID(3, 0)) << version;
if (version < 40) {
EXPECT_EQ(transition.name, "transition") << version;
EXPECT_FLOAT_EQ(transition.width, 2.5f) << version;
}
EXPECT_FALSE(zone->GetZoneRaw().chunks.empty()) << version;
}
}
TEST_F(ZoneSceneTest, SceneAtPosition) {
WriteZone();
WriteFile("test.raw", MakeRaw());