fix(zone): adjust path values as the client does while reading them

LevelPath::FromBuffer (1.10.64) changes two values as it reads a path:
- a property path's reputation multiplier outside 0-1000 becomes 0;
- a camera waypoint's FOV in a path older than version 11 is divided
  by 1.25.

The reader kept both as written. Only camera paths of older client
files (versions 8-10) change; no live zone has either case.

Check in game: nothing to check (no live zone changes).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-09-29 06:11:12 -05:00
parent 83c2136e9a
commit 13a3d18789
2 changed files with 33 additions and 0 deletions

View File

@@ -279,6 +279,8 @@ bool ZoneFile::ReadPath(std::istream& file) {
if (path.pathVersion >= 7) {
BinaryIO::BinaryRead(file, path.property.cloneLimit);
BinaryIO::BinaryRead(file, path.property.repMultiplier);
// The client takes a multiplier outside 0-1000 as 0 (LevelPath::FromBuffer)
if (path.property.repMultiplier < 0.0f || path.property.repMultiplier > 1000.0f) path.property.repMultiplier = 0.0f;
BinaryIO::BinaryRead(file, path.property.rentalPeriod);
}
@@ -346,6 +348,8 @@ bool ZoneFile::ReadPath(std::istream& file) {
} else if (path.pathType == PathType::Camera) {
BinaryIO::BinaryRead(file, waypoint.camera.time);
BinaryIO::BinaryRead(file, waypoint.camera.fov);
// Before version 11 the file's FOV is 1.25 times the one the client uses (LevelPath::FromBuffer)
if (path.pathVersion < 11) waypoint.camera.fov /= 1.25f;
BinaryIO::BinaryRead(file, waypoint.camera.tension);
BinaryIO::BinaryRead(file, waypoint.camera.continuity);
BinaryIO::BinaryRead(file, waypoint.camera.bias);

View File

@@ -329,6 +329,35 @@ TEST(ZoneFileTests, SpawnerAndCameraFieldsFromVersion4) {
EXPECT_EQ(zone.paths[1].pathWaypoints.at(0).position, NiPoint3(2, 2, 2));
}
// Values the client adjusts as it reads them: a reputation multiplier outside 0-1000 is 0, a camera FOV before path
// version 11 is divided by 1.25
TEST(ZoneFileTests, AdjustsValuesAsTheClient) {
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>(0).Text("zone.raw").Text("Name").Text("Description");
w.Put<uint32_t>(0).Paths().Put<uint32_t>(1).Put<uint32_t>(3);
for (const float multiplier : { 2000.0f, 3.0f }) {
w.Put<uint32_t>(8).Wide("Property").Put<uint32_t>(2).Put<uint32_t>(0).Put<uint32_t>(0);
w.Put<int32_t>(0).Put<int32_t>(0).Put<uint32_t>(0).Put<uint64_t>(0).Wide("").Put<uint32_t>(0);
w.Put<int32_t>(0).Put<uint32_t>(0).Put(multiplier).Put<uint32_t>(0).Put<uint32_t>(0).Point(0, 0, 0).Put(128.0f);
w.Put<uint32_t>(0);
}
w.Put<uint32_t>(10).Wide("Camera").Put<uint32_t>(3).Put<uint32_t>(0).Put<uint32_t>(0).Wide("");
w.Put<uint32_t>(1).Point(0, 0, 0).Put(1.0f).Put(0.0f).Put(0.0f).Put(0.0f).Put(1.0f).Put(50.0f).Put(0.0f).Put(0.0f).Put(0.0f);
std::istringstream stream(w.Done());
ZoneFile zone;
zone.Read(stream);
EXPECT_FALSE(stream.fail());
ASSERT_EQ(zone.paths.size(), 3u);
EXPECT_EQ(zone.paths[0].property.repMultiplier, 0.0f);
EXPECT_EQ(zone.paths[1].property.repMultiplier, 3.0f);
EXPECT_EQ(zone.paths[2].pathWaypoints.at(0).camera.fov, 40.0f);
}
// The paths are a chunk the client reads on its own: a path it refuses (here version 19) leaves the zone with no paths,
// and the zone file goes on after the chunk
TEST(ZoneFileTests, RefusedPathsLeaveNoPaths) {