mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-10 06:43:43 +00:00
feat(zone): read the zone boundary lines of .luz files
Between the scenes and the terrain file's name a zone file has its boundary lines: a u8 count, then per line a normal, a point, the destination zone, the destination scene ID and a spawn location. The reader took that spot for a u8-length "zone path" string, which is the same bytes only when there are no boundaries; a zone with boundaries would have misread everything after it. The destination is one u32 in the client (LuzReader::ReadZoneBoundaryLines, 0x01018490 in 1.10.64: map ID in bits 0-15, instance ID in bits 16-31, clone 0), read here as two u16s into an LWOZONEID with clone 0. The boundaries are kept on ZoneFile::zoneBoundaries. No zone of the 1.10.64 client (or any other client on disk) has boundary lines, so what is read from them is unchanged. Check in game: every world loads and zone transfers (launch pads, rockets, portals) work as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -31,8 +31,9 @@ void ZoneFile::ReadHeader(std::istream& file) {
|
||||
ReadScene(file);
|
||||
}
|
||||
|
||||
ReadZoneBoundaries(file);
|
||||
|
||||
//Read generic zone info:
|
||||
BinaryIO::ReadString<uint8_t>(file, zonePath, BinaryIO::ReadType::String);
|
||||
BinaryIO::ReadString<uint8_t>(file, zoneRawPath, BinaryIO::ReadType::String);
|
||||
BinaryIO::ReadString<uint8_t>(file, zoneName, BinaryIO::ReadType::String);
|
||||
BinaryIO::ReadString<uint8_t>(file, zoneDesc, BinaryIO::ReadType::String);
|
||||
@@ -89,6 +90,26 @@ void ZoneFile::ReadScene(std::istream& file) {
|
||||
scenes.push_back(std::move(scene));
|
||||
}
|
||||
|
||||
void ZoneFile::ReadZoneBoundaries(std::istream& file) {
|
||||
uint8_t count = 0;
|
||||
BinaryIO::BinaryRead(file, count);
|
||||
zoneBoundaries.reserve(count);
|
||||
for (uint8_t i = 0; i < count; ++i) {
|
||||
ZoneBoundary boundary;
|
||||
BinaryIO::BinaryRead(file, boundary.normal);
|
||||
BinaryIO::BinaryRead(file, boundary.point);
|
||||
// The client reads one u32 of map ID (low 16 bits) and instance ID (high 16 bits)
|
||||
LWOMAPID mapID = 0;
|
||||
LWOINSTANCEID instanceID = 0;
|
||||
BinaryIO::BinaryRead(file, mapID);
|
||||
BinaryIO::BinaryRead(file, instanceID);
|
||||
boundary.destZoneID = LWOZONEID(mapID, instanceID, 0);
|
||||
BinaryIO::BinaryRead(file, boundary.destSceneID);
|
||||
BinaryIO::BinaryRead(file, boundary.spawnLocation);
|
||||
zoneBoundaries.push_back(boundary);
|
||||
}
|
||||
}
|
||||
|
||||
void ZoneFile::ReadSceneTransition(std::istream& file) {
|
||||
SceneTransition sceneTrans;
|
||||
if (fileFormatVersion < FileFormatVersion::Auramar) {
|
||||
|
||||
@@ -35,6 +35,15 @@ struct ZoneScene {
|
||||
uint8_t color_b{};
|
||||
};
|
||||
|
||||
// A line the player crosses to leave the zone (the client's LuzReader::ReadZoneBoundaryLines)
|
||||
struct ZoneBoundary {
|
||||
NiPoint3 normal;
|
||||
NiPoint3 point;
|
||||
LWOZONEID destZoneID; // clone 0
|
||||
uint32_t destSceneID{};
|
||||
NiPoint3 spawnLocation;
|
||||
};
|
||||
|
||||
struct SceneTransitionInfo {
|
||||
uint64_t sceneID{}; //id of the scene being transitioned to.
|
||||
NiPoint3 position;
|
||||
@@ -209,7 +218,8 @@ struct ZoneFile {
|
||||
|
||||
std::vector<ZoneScene> scenes;
|
||||
|
||||
std::string zonePath; //Path to the .luz's folder as written in the file
|
||||
std::vector<ZoneBoundary> zoneBoundaries;
|
||||
|
||||
std::string zoneRawPath; //Path to the .raw file of this zone.
|
||||
std::string zoneName; //Name given to the zone by a level designer
|
||||
std::string zoneDesc; //Description of the zone by a level designer
|
||||
@@ -234,6 +244,7 @@ struct ZoneFile {
|
||||
|
||||
private:
|
||||
void ReadScene(std::istream& file);
|
||||
void ReadZoneBoundaries(std::istream& file);
|
||||
void ReadSceneTransition(std::istream& file);
|
||||
SceneTransitionInfo ReadSceneTransitionInfo(std::istream& file);
|
||||
void ReadPath(std::istream& file);
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace {
|
||||
w.Point(1, 2, 3).Put(1.0f).Put(0.0f).Put(0.0f).Put(0.0f); // spawn point and rotation
|
||||
w.Put<uint32_t>(1);
|
||||
w.Text("scene.lvl").Put<uint32_t>(7).Put<uint32_t>(2).Text("Global").Put<uint8_t>(1).Put<uint8_t>(2).Put<uint8_t>(3);
|
||||
w.Text("zone").Text("zone.raw").Text("Name").Text("Description");
|
||||
w.Put<uint8_t>(0).Text("zone.raw").Text("Name").Text("Description"); // no zone boundaries
|
||||
w.Put<uint32_t>(1);
|
||||
for (int i = 0; i < 2; i++) w.Put<uint64_t>(1).Point(0, 0, 0);
|
||||
w.Put<uint32_t>(0).Put<uint32_t>(1).Put<uint32_t>(3);
|
||||
@@ -89,6 +89,36 @@ TEST(ZoneFileTests, ReadsScenesAndPaths) {
|
||||
EXPECT_EQ(property.pathWaypoints.size(), 3u);
|
||||
}
|
||||
|
||||
// Boundary lines sit between the scenes and the terrain file's name
|
||||
TEST(ZoneFileTests, ReadsZoneBoundaries) {
|
||||
ZoneBytes w;
|
||||
w.Put<uint32_t>(41).Put<uint32_t>(3).Put<uint32_t>(1150);
|
||||
w.Point(0, 0, 0).Put(1.0f).Put(0.0f).Put(0.0f).Put(0.0f);
|
||||
w.Put<uint32_t>(1);
|
||||
w.Text("scene.lvl").Put<uint32_t>(0).Put<uint32_t>(0).Text("Global").Put<uint8_t>(0).Put<uint8_t>(0).Put<uint8_t>(0);
|
||||
w.Put<uint8_t>(2);
|
||||
w.Point(1, 0, 0).Point(10, 20, 30).Put<uint16_t>(1100).Put<uint16_t>(7).Put<uint32_t>(4).Point(5, 6, 7);
|
||||
w.Point(0, 0, -1).Point(-1, -2, -3).Put<uint16_t>(1200).Put<uint16_t>(0).Put<uint32_t>(0).Point(0, 0, 0);
|
||||
w.Text("zone.raw").Text("Name").Text("Description");
|
||||
w.Put<uint32_t>(0).Put<uint32_t>(0).Put<uint32_t>(1).Put<uint32_t>(0); // no transitions, no paths
|
||||
|
||||
std::istringstream stream(w.data);
|
||||
ZoneFile zone;
|
||||
zone.Read(stream);
|
||||
EXPECT_FALSE(stream.fail());
|
||||
ASSERT_EQ(zone.zoneBoundaries.size(), 2u);
|
||||
const auto& boundary = zone.zoneBoundaries[0];
|
||||
EXPECT_EQ(boundary.normal, NiPoint3(1, 0, 0));
|
||||
EXPECT_EQ(boundary.point, NiPoint3(10, 20, 30));
|
||||
EXPECT_EQ(boundary.destZoneID, LWOZONEID(1100, 7, 0));
|
||||
EXPECT_EQ(boundary.destSceneID, 4u);
|
||||
EXPECT_EQ(boundary.spawnLocation, NiPoint3(5, 6, 7));
|
||||
EXPECT_EQ(zone.zoneBoundaries[1].destZoneID, LWOZONEID(1200, 0, 0));
|
||||
EXPECT_EQ(zone.zoneRawPath, "zone.raw");
|
||||
EXPECT_EQ(zone.zoneName, "Name");
|
||||
EXPECT_EQ(zone.zoneDesc, "Description");
|
||||
}
|
||||
|
||||
TEST(ZoneFileTests, ShortFilesThrowOrFail) {
|
||||
const auto zone = SampleZone();
|
||||
for (const size_t length : { size_t{ 3 }, size_t{ 40 }, zone.size() / 2, zone.size() - 1 }) {
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace {
|
||||
w.Point(1, 2, 3).Put(1.0f).Put(0.0f).Put(0.0f).Put(0.0f); // spawn point and rotation
|
||||
w.Put<uint32_t>(1); // one scene
|
||||
w.Text("scene.lvl").Put<uint32_t>(1).Put<uint32_t>(0).Text("Global").Put<uint8_t>(1).Put<uint8_t>(2).Put<uint8_t>(3);
|
||||
w.Text("zone").Text("zone.raw").Text("Name").Text("Description");
|
||||
w.Put<uint8_t>(0).Text("zone.raw").Text("Name").Text("Description"); // no zone boundaries
|
||||
w.Put<uint32_t>(1); // one transition: 2 points in this version
|
||||
for (int i = 0; i < 2; i++) w.Put<uint64_t>(1).Point(0, 0, 0);
|
||||
w.Put<uint32_t>(0).Put<uint32_t>(1).Put<uint32_t>(3); // path data length, chunk version, 3 paths
|
||||
|
||||
Reference in New Issue
Block a user