From 9f390958f5264a3d10946047c368e56f4394b81f Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Tue, 29 Sep 2026 05:11:27 -0500 Subject: [PATCH] fix(zone): scenes of version 30-32 zone files are numbered like the client Zone files before version 33 have no scene IDs. The client gives each scene its index in the file as its ID, -1 past 255, with layer 0 (LuzReader::ReadScenes, 1.10.64, via the LWOSceneID from the loop index); the reader left every such scene at ID 0, so a zone with several scenes had them all collide on one ID. The version 30 zones on disk have one scene each, so what they read is unchanged. Check in game: nothing to check on live worlds (all are version 36+). Co-Authored-By: Claude Opus 5.5 --- dCommon/ZoneFile.cpp | 6 ++++-- dCommon/ZoneFile.h | 2 +- dZoneManager/Zone.cpp | 2 +- tests/dCommonTests/ZoneFileTests.cpp | 16 ++++++++++++++++ 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/dCommon/ZoneFile.cpp b/dCommon/ZoneFile.cpp index 8f1069d79..12825e3dc 100644 --- a/dCommon/ZoneFile.cpp +++ b/dCommon/ZoneFile.cpp @@ -33,7 +33,7 @@ void ZoneFile::ReadHeader(std::istream& file) { } else BinaryIO::BinaryRead(file, sceneCount); for (uint32_t i = 0; i < sceneCount; ++i) { - ReadScene(file); + ReadScene(file, i); } ReadZoneBoundaries(file); @@ -70,7 +70,7 @@ void ZoneFile::Read(std::istream& file) { } } -void ZoneFile::ReadScene(std::istream& file) { +void ZoneFile::ReadScene(std::istream& file, uint32_t index) { ZoneScene scene; // Before PrePreAlpha a scene is only its SceneTable ID; ResolveSceneTable finds its file (LuzReader::ReadScenes) @@ -82,6 +82,8 @@ void ZoneFile::ReadScene(std::istream& file) { } BinaryIO::ReadString(file, scene.filename, BinaryIO::ReadType::String); + // Until LatePreAlpha the file has no scene IDs: a scene's ID is its index (-1 past 255) in the file + scene.id = index < 256 ? index : UINT32_MAX; if (fileFormatVersion >= FileFormatVersion::LatePreAlpha) { BinaryIO::BinaryRead(file, scene.id); diff --git a/dCommon/ZoneFile.h b/dCommon/ZoneFile.h index eac96885e..e560a93f4 100644 --- a/dCommon/ZoneFile.h +++ b/dCommon/ZoneFile.h @@ -255,7 +255,7 @@ struct ZoneFile { void ResolveSceneTable(const std::function(uint32_t)>& sceneName); private: - void ReadScene(std::istream& file); + void ReadScene(std::istream& file, uint32_t index); void ReadZoneBoundaries(std::istream& file); void ReadSceneTransition(std::istream& file); SceneTransitionInfo ReadSceneTransitionInfo(std::istream& file); diff --git a/dZoneManager/Zone.cpp b/dZoneManager/Zone.cpp index 289c86cd2..8582dcac1 100644 --- a/dZoneManager/Zone.cpp +++ b/dZoneManager/Zone.cpp @@ -243,7 +243,7 @@ void Zone::LoadScene(ZoneScene&& zoneScene) { SceneRef scene; static_cast(scene) = std::move(zoneScene); scene.level = nullptr; - // Older files have no scene ID or layer; those stay 0 + // Older files have no scene ID or layer: ZoneFile numbers their scenes, and the layer stays General LWOSCENEID lwoSceneID(scene.id, scene.sceneType); std::string luTriggersPath = scene.filename.substr(0, scene.filename.size() - 4) + ".lutriggers"; diff --git a/tests/dCommonTests/ZoneFileTests.cpp b/tests/dCommonTests/ZoneFileTests.cpp index 20e8baf8f..646741034 100644 --- a/tests/dCommonTests/ZoneFileTests.cpp +++ b/tests/dCommonTests/ZoneFileTests.cpp @@ -167,6 +167,22 @@ TEST(ZoneFileTests, ReadsVersionsBeforePrePreAlpha) { EXPECT_EQ(zone.scenes[1].filename, "scene9.lvl"); } +// Versions 30-32 have no scene IDs: the client numbers the scenes in file order +TEST(ZoneFileTests, EarlyScenesAreNumberedInOrder) { + ZoneBytes w; + w.Put(32).Put(70).Put(3).Text("a.lvl").Text("b.lvl").Text("c.lvl"); + w.Put(0).Text("zone.raw").Text("Name").Text("Description").Put(0); + std::istringstream stream(w.data); + ZoneFile zone; + zone.Read(stream); + EXPECT_FALSE(stream.fail()); + ASSERT_EQ(zone.scenes.size(), 3u); + EXPECT_EQ(zone.scenes[0].id, 0u); + EXPECT_EQ(zone.scenes[1].id, 1u); + EXPECT_EQ(zone.scenes[2].id, 2u); + EXPECT_EQ(zone.scenes[2].filename, "c.lvl"); +} + // A PrePreAlpha (30) file ends at its terrain file's name: no zone name, description, transitions or paths TEST(ZoneFileTests, PrePreAlphaHasNoZoneName) { ZoneBytes w;