mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-11-06 11:57:13 +00:00
fix: read a zone's paths as the client does
LuzFile::ReadLUZFile copies the path chunk by its size and ReadLUZPaths
reads it with LevelPath::FromBuffer (1.10.64). Reading as they do:
- the paths are read from their chunk, never past its end;
- a chunk version of 2 or more, 10000 paths or more, or a path the
client refuses (version 19 or more, 10000 waypoints or more, more
than 99 name/value pairs on a waypoint) leaves the zone with no
paths, logged; the rest of the zone file still reads;
- before version 3 a path's type is a name ("platform" or a movement
path) and every waypoint has a rotation, the moving platform values
and a name/value list, whatever its type;
- property headers are there from version 3, spawner and camera
headers from version 4;
- a camera waypoint's FOV before version 11 is divided by 1.25;
- a property path's reputation multiplier outside 0-1000 becomes 0.
Every path chunk of every client's zone files on disk (858) reads to
its end.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -140,14 +140,17 @@ void Zone::LoadZoneIntoMemory() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (m_FileFormatVersion >= Zone::FileFormatVersion::EarlyAlpha) {
|
if (m_FileFormatVersion >= Zone::FileFormatVersion::EarlyAlpha) {
|
||||||
|
// The paths are a chunk of their own (LuzFile::ReadLUZFile copies it, ReadLUZPaths reads it)
|
||||||
BinaryIO::BinaryRead(file, m_PathDataLength);
|
BinaryIO::BinaryRead(file, m_PathDataLength);
|
||||||
BinaryIO::BinaryRead(file, m_PathChunkVersion); // always should be 1
|
std::string chunk(m_PathDataLength, '\0');
|
||||||
|
file.read(chunk.data(), static_cast<std::streamsize>(chunk.size()));
|
||||||
uint32_t pathCount;
|
if (!file) throw std::runtime_error("Failed to read from istream.");
|
||||||
BinaryIO::BinaryRead(file, pathCount);
|
std::istringstream pathChunk(chunk);
|
||||||
|
// Like the client, a path chunk it can't read gives the zone no paths
|
||||||
m_Paths.reserve(pathCount);
|
if (!LoadPaths(pathChunk)) {
|
||||||
for (uint32_t i = 0; i < pathCount; ++i) LoadPath(file);
|
LOG("Zone %s has a path chunk the client doesn't read; no paths loaded", m_ZoneFilePath.c_str());
|
||||||
|
m_Paths.clear();
|
||||||
|
}
|
||||||
|
|
||||||
for (const Path& path : m_Paths) {
|
for (const Path& path : m_Paths) {
|
||||||
if (path.pathType != PathType::Spawner) continue;
|
if (path.pathType != PathType::Spawner) continue;
|
||||||
@@ -409,14 +412,43 @@ SceneTransitionInfo Zone::LoadSceneTransitionInfo(std::istream& file) {
|
|||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Zone::LoadPath(std::istream& file) {
|
bool Zone::LoadPaths(std::istream& file) {
|
||||||
|
// ReadLUZPaths: a chunk version from 2 on, 10000 paths or more, or any path LevelPath::FromBuffer refuses (it reads
|
||||||
|
// no further than the chunk's end here) leaves the zone with no paths
|
||||||
|
try {
|
||||||
|
BinaryIO::BinaryRead(file, m_PathChunkVersion);
|
||||||
|
if (m_PathChunkVersion >= 2) return false;
|
||||||
|
|
||||||
|
uint32_t pathCount;
|
||||||
|
BinaryIO::BinaryRead(file, pathCount);
|
||||||
|
if (pathCount >= 10000) return false;
|
||||||
|
|
||||||
|
m_Paths.reserve(pathCount);
|
||||||
|
for (uint32_t i = 0; i < pathCount; ++i) {
|
||||||
|
if (!LoadPath(file)) return false;
|
||||||
|
}
|
||||||
|
} catch (const std::runtime_error&) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Zone::LoadPath(std::istream& file) {
|
||||||
Path path = Path();
|
Path path = Path();
|
||||||
|
|
||||||
BinaryIO::BinaryRead(file, path.pathVersion);
|
BinaryIO::BinaryRead(file, path.pathVersion);
|
||||||
|
if (path.pathVersion >= 19) return false;
|
||||||
|
|
||||||
BinaryIO::ReadString<uint8_t>(file, path.pathName, BinaryIO::ReadType::WideString);
|
BinaryIO::ReadString<uint8_t>(file, path.pathName, BinaryIO::ReadType::WideString);
|
||||||
|
|
||||||
BinaryIO::BinaryRead(file, path.pathType);
|
// Before version 3 the type is a name: "platform" or a movement path
|
||||||
|
if (path.pathVersion < 3) {
|
||||||
|
std::string type;
|
||||||
|
BinaryIO::ReadString<uint8_t>(file, type, BinaryIO::ReadType::WideString);
|
||||||
|
path.pathType = type == "platform" ? PathType::MovingPlatform : PathType::Movement;
|
||||||
|
} else {
|
||||||
|
BinaryIO::BinaryRead(file, path.pathType);
|
||||||
|
}
|
||||||
BinaryIO::BinaryRead(file, path.flags);
|
BinaryIO::BinaryRead(file, path.flags);
|
||||||
BinaryIO::BinaryRead(file, path.pathBehavior);
|
BinaryIO::BinaryRead(file, path.pathBehavior);
|
||||||
|
|
||||||
@@ -426,7 +458,7 @@ void Zone::LoadPath(std::istream& file) {
|
|||||||
} else if (path.pathVersion >= 13) {
|
} else if (path.pathVersion >= 13) {
|
||||||
BinaryIO::ReadString<uint8_t>(file, path.movingPlatform.platformTravelSound, BinaryIO::ReadType::WideString);
|
BinaryIO::ReadString<uint8_t>(file, path.movingPlatform.platformTravelSound, BinaryIO::ReadType::WideString);
|
||||||
}
|
}
|
||||||
} else if (path.pathType == PathType::Property) {
|
} else if (path.pathType == PathType::Property && path.pathVersion >= 3) {
|
||||||
BinaryIO::BinaryRead(file, path.property.pathType);
|
BinaryIO::BinaryRead(file, path.property.pathType);
|
||||||
BinaryIO::BinaryRead(file, path.property.price);
|
BinaryIO::BinaryRead(file, path.property.price);
|
||||||
BinaryIO::BinaryRead(file, path.property.rentalTime);
|
BinaryIO::BinaryRead(file, path.property.rentalTime);
|
||||||
@@ -442,6 +474,8 @@ void Zone::LoadPath(std::istream& file) {
|
|||||||
if (path.pathVersion >= 7) {
|
if (path.pathVersion >= 7) {
|
||||||
BinaryIO::BinaryRead(file, path.property.cloneLimit);
|
BinaryIO::BinaryRead(file, path.property.cloneLimit);
|
||||||
BinaryIO::BinaryRead(file, path.property.repMultiplier);
|
BinaryIO::BinaryRead(file, path.property.repMultiplier);
|
||||||
|
// The client takes a multiplier outside 0-1000 as 0
|
||||||
|
if (path.property.repMultiplier < 0.0f || path.property.repMultiplier > 1000.0f) path.property.repMultiplier = 0.0f;
|
||||||
BinaryIO::BinaryRead(file, path.property.rentalPeriod);
|
BinaryIO::BinaryRead(file, path.property.rentalPeriod);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -450,25 +484,28 @@ void Zone::LoadPath(std::istream& file) {
|
|||||||
BinaryIO::BinaryRead(file, path.property.playerZoneCoords);
|
BinaryIO::BinaryRead(file, path.property.playerZoneCoords);
|
||||||
BinaryIO::BinaryRead(file, path.property.maxBuildHeight);
|
BinaryIO::BinaryRead(file, path.property.maxBuildHeight);
|
||||||
}
|
}
|
||||||
} else if (path.pathType == PathType::Camera) {
|
} else if (path.pathType == PathType::Camera && path.pathVersion >= 4) {
|
||||||
BinaryIO::ReadString<uint8_t>(file, path.camera.nextPath, BinaryIO::ReadType::WideString);
|
BinaryIO::ReadString<uint8_t>(file, path.camera.nextPath, BinaryIO::ReadType::WideString);
|
||||||
if (path.pathVersion >= 14) {
|
if (path.pathVersion >= 14) {
|
||||||
BinaryIO::BinaryRead(file, path.camera.rotatePlayer);
|
BinaryIO::BinaryRead(file, path.camera.rotatePlayer);
|
||||||
|
|
||||||
}
|
}
|
||||||
} else if (path.pathType == PathType::Spawner) {
|
} else if (path.pathType == PathType::Spawner && path.pathVersion >= 4) {
|
||||||
BinaryIO::BinaryRead(file, path.spawner.spawnedLOT);
|
BinaryIO::BinaryRead(file, path.spawner.spawnedLOT);
|
||||||
BinaryIO::BinaryRead(file, path.spawner.respawnTime);
|
BinaryIO::BinaryRead(file, path.spawner.respawnTime);
|
||||||
BinaryIO::BinaryRead(file, path.spawner.maxToSpawn);
|
BinaryIO::BinaryRead(file, path.spawner.maxToSpawn);
|
||||||
BinaryIO::BinaryRead(file, path.spawner.amountMaintained);
|
BinaryIO::BinaryRead(file, path.spawner.amountMaintained);
|
||||||
BinaryIO::BinaryRead(file, path.spawner.spawnerObjID);
|
BinaryIO::BinaryRead(file, path.spawner.spawnerObjID);
|
||||||
BinaryIO::BinaryRead(file, path.spawner.spawnerNetActive);
|
if (path.pathVersion >= 9) BinaryIO::BinaryRead(file, path.spawner.spawnerNetActive);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read waypoints
|
// Read waypoints
|
||||||
|
|
||||||
BinaryIO::BinaryRead(file, path.waypointCount);
|
BinaryIO::BinaryRead(file, path.waypointCount);
|
||||||
|
if (path.waypointCount >= 10000) return false;
|
||||||
path.pathWaypoints.reserve(path.waypointCount);
|
path.pathWaypoints.reserve(path.waypointCount);
|
||||||
|
// Before version 3 every waypoint has the moving platform values and a command list, whatever the type
|
||||||
|
const bool allWaypointValues = path.pathVersion < 3;
|
||||||
for (uint32_t i = 0; i < path.waypointCount; ++i) {
|
for (uint32_t i = 0; i < path.waypointCount; ++i) {
|
||||||
PathWaypoint waypoint = PathWaypoint();
|
PathWaypoint waypoint = PathWaypoint();
|
||||||
|
|
||||||
@@ -476,14 +513,14 @@ void Zone::LoadPath(std::istream& file) {
|
|||||||
BinaryIO::BinaryRead(file, waypoint.position.y);
|
BinaryIO::BinaryRead(file, waypoint.position.y);
|
||||||
BinaryIO::BinaryRead(file, waypoint.position.z);
|
BinaryIO::BinaryRead(file, waypoint.position.z);
|
||||||
|
|
||||||
if (path.pathType == PathType::Spawner || path.pathType == PathType::MovingPlatform || path.pathType == PathType::Race || path.pathType == PathType::Camera || path.pathType == PathType::Rail) {
|
if (allWaypointValues || path.pathType == PathType::Spawner || path.pathType == PathType::MovingPlatform || path.pathType == PathType::Race || path.pathType == PathType::Camera || path.pathType == PathType::Rail) {
|
||||||
BinaryIO::BinaryRead(file, waypoint.rotation.w);
|
BinaryIO::BinaryRead(file, waypoint.rotation.w);
|
||||||
BinaryIO::BinaryRead(file, waypoint.rotation.x);
|
BinaryIO::BinaryRead(file, waypoint.rotation.x);
|
||||||
BinaryIO::BinaryRead(file, waypoint.rotation.y);
|
BinaryIO::BinaryRead(file, waypoint.rotation.y);
|
||||||
BinaryIO::BinaryRead(file, waypoint.rotation.z);
|
BinaryIO::BinaryRead(file, waypoint.rotation.z);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path.pathType == PathType::MovingPlatform) {
|
if (allWaypointValues || path.pathType == PathType::MovingPlatform) {
|
||||||
BinaryIO::BinaryRead(file, waypoint.movingPlatform.lockPlayer);
|
BinaryIO::BinaryRead(file, waypoint.movingPlatform.lockPlayer);
|
||||||
BinaryIO::BinaryRead(file, waypoint.speed);
|
BinaryIO::BinaryRead(file, waypoint.speed);
|
||||||
BinaryIO::BinaryRead(file, waypoint.movingPlatform.wait);
|
BinaryIO::BinaryRead(file, waypoint.movingPlatform.wait);
|
||||||
@@ -494,6 +531,8 @@ void Zone::LoadPath(std::istream& file) {
|
|||||||
} else if (path.pathType == PathType::Camera) {
|
} else if (path.pathType == PathType::Camera) {
|
||||||
BinaryIO::BinaryRead(file, waypoint.camera.time);
|
BinaryIO::BinaryRead(file, waypoint.camera.time);
|
||||||
BinaryIO::BinaryRead(file, waypoint.camera.fov);
|
BinaryIO::BinaryRead(file, waypoint.camera.fov);
|
||||||
|
// Before version 11 the field of view is 1.25 times larger
|
||||||
|
if (path.pathVersion < 11) waypoint.camera.fov /= 1.25f;
|
||||||
BinaryIO::BinaryRead(file, waypoint.camera.tension);
|
BinaryIO::BinaryRead(file, waypoint.camera.tension);
|
||||||
BinaryIO::BinaryRead(file, waypoint.camera.continuity);
|
BinaryIO::BinaryRead(file, waypoint.camera.continuity);
|
||||||
BinaryIO::BinaryRead(file, waypoint.camera.bias);
|
BinaryIO::BinaryRead(file, waypoint.camera.bias);
|
||||||
@@ -508,9 +547,10 @@ void Zone::LoadPath(std::istream& file) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// object LDF configs
|
// object LDF configs
|
||||||
if (path.pathType == PathType::Movement || path.pathType == PathType::Spawner || path.pathType == PathType::Rail) {
|
if (allWaypointValues || path.pathType == PathType::Movement || path.pathType == PathType::Spawner || path.pathType == PathType::Rail) {
|
||||||
uint32_t count;
|
uint32_t count;
|
||||||
BinaryIO::BinaryRead(file, count);
|
BinaryIO::BinaryRead(file, count);
|
||||||
|
if (count > 99) return false;
|
||||||
for (uint32_t i = 0; i < count; ++i) {
|
for (uint32_t i = 0; i < count; ++i) {
|
||||||
std::string parameter;
|
std::string parameter;
|
||||||
BinaryIO::ReadString<uint8_t>(file, parameter, BinaryIO::ReadType::WideString);
|
BinaryIO::ReadString<uint8_t>(file, parameter, BinaryIO::ReadType::WideString);
|
||||||
@@ -518,7 +558,7 @@ void Zone::LoadPath(std::istream& file) {
|
|||||||
std::string value;
|
std::string value;
|
||||||
BinaryIO::ReadString<uint8_t>(file, value, BinaryIO::ReadType::WideString);
|
BinaryIO::ReadString<uint8_t>(file, value, BinaryIO::ReadType::WideString);
|
||||||
|
|
||||||
if (path.pathType == PathType::Movement || path.pathType == PathType::Rail) {
|
if (path.pathType != PathType::Spawner) {
|
||||||
// cause NetDevil puts spaces in things that don't need spaces
|
// cause NetDevil puts spaces in things that don't need spaces
|
||||||
parameter.erase(std::remove_if(parameter.begin(), parameter.end(), ::isspace), parameter.end());
|
parameter.erase(std::remove_if(parameter.begin(), parameter.end(), ::isspace), parameter.end());
|
||||||
auto waypointCommand = WaypointCommandType::StringToWaypointCommandType(parameter);
|
auto waypointCommand = WaypointCommandType::StringToWaypointCommandType(parameter);
|
||||||
@@ -546,6 +586,7 @@ void Zone::LoadPath(std::istream& file) {
|
|||||||
path.pathWaypoints.push_back(waypoint);
|
path.pathWaypoints.push_back(waypoint);
|
||||||
}
|
}
|
||||||
m_Paths.push_back(path);
|
m_Paths.push_back(path);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SceneRef* Zone::GetScene(LWOSCENEID sceneID) const {
|
const SceneRef* Zone::GetScene(LWOSCENEID sceneID) const {
|
||||||
|
|||||||
@@ -280,5 +280,6 @@ private:
|
|||||||
void LoadLUTriggers(std::string triggerFile, SceneRef& scene);
|
void LoadLUTriggers(std::string triggerFile, SceneRef& scene);
|
||||||
void LoadSceneTransition(std::istream& file);
|
void LoadSceneTransition(std::istream& file);
|
||||||
SceneTransitionInfo LoadSceneTransitionInfo(std::istream& file);
|
SceneTransitionInfo LoadSceneTransitionInfo(std::istream& file);
|
||||||
void LoadPath(std::istream& file);
|
bool LoadPaths(std::istream& file);
|
||||||
|
bool LoadPath(std::istream& file);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
SET(DZONEMANAGER_TESTS
|
SET(DZONEMANAGER_TESTS
|
||||||
"RawTests.cpp"
|
"RawTests.cpp"
|
||||||
|
"ZonePathTests.cpp"
|
||||||
"ZoneSceneTests.cpp")
|
"ZoneSceneTests.cpp")
|
||||||
|
|
||||||
# Get the folder name and prepend it to the files above
|
# Get the folder name and prepend it to the files above
|
||||||
|
|||||||
228
tests/dGameTests/dZoneManagerTests/ZonePathTests.cpp
Normal file
228
tests/dGameTests/dZoneManagerTests/ZonePathTests.cpp
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
#include "GameDependencies.h"
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#include "AssetManager.h"
|
||||||
|
#include "CDZoneTableTable.h"
|
||||||
|
#include "Zone.h"
|
||||||
|
#include "dZoneManager.h"
|
||||||
|
#include "eWaypointCommandType.h"
|
||||||
|
|
||||||
|
// The zone table's storage (CDZoneTableTable.cpp), to add the test zone
|
||||||
|
namespace CDZoneTableTable {
|
||||||
|
extern Table entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
template<typename T>
|
||||||
|
void Write(std::ostream& stream, const T& value) {
|
||||||
|
stream.write(reinterpret_cast<const char*>(&value), sizeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
void WriteString8(std::ostream& stream, const std::string& value) {
|
||||||
|
Write<uint8_t>(stream, static_cast<uint8_t>(value.size()));
|
||||||
|
stream.write(value.data(), value.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
// A u8 length, then that many UTF-16 characters (ASCII only)
|
||||||
|
void WriteWString8(std::ostream& stream, const std::string& value) {
|
||||||
|
Write<uint8_t>(stream, static_cast<uint8_t>(value.size()));
|
||||||
|
for (const char c : value) Write<uint16_t>(stream, static_cast<uint16_t>(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
// The waypoint every version 2 path has: rotation, the moving platform values and one command
|
||||||
|
void WriteOldWaypoint(std::ostream& stream, float x) {
|
||||||
|
Write(stream, NiPoint3(x, 0.0f, 0.0f));
|
||||||
|
for (const float value : { 1.0f, 0.0f, 0.0f, 0.0f }) Write(stream, value); // rotation
|
||||||
|
Write<uint8_t>(stream, 1); // lock player
|
||||||
|
Write(stream, 3.0f); // speed
|
||||||
|
Write(stream, 4.0f); // wait
|
||||||
|
Write<uint32_t>(stream, 1);
|
||||||
|
WriteWString8(stream, "delay");
|
||||||
|
WriteWString8(stream, "2");
|
||||||
|
}
|
||||||
|
|
||||||
|
void WritePathHeader(std::ostream& stream, uint32_t version, const std::string& name, PathType type) {
|
||||||
|
Write(stream, version);
|
||||||
|
WriteWString8(stream, name);
|
||||||
|
Write(stream, type);
|
||||||
|
Write<uint32_t>(stream, 0); // flags
|
||||||
|
Write<uint32_t>(stream, 0); // behavior
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loads a written .luz (version 41, no scenes) with the given path chunk through the zone manager
|
||||||
|
class ZonePathTest : public GameDependenciesTest {
|
||||||
|
protected:
|
||||||
|
static constexpr LWOMAPID TEST_MAP = 9992;
|
||||||
|
std::filesystem::path root;
|
||||||
|
|
||||||
|
void SetUp() override {
|
||||||
|
SetUpDependencies();
|
||||||
|
root = std::filesystem::temp_directory_path() / (std::string("dlu_zone_path_test_") + ::testing::UnitTest::GetInstance()->current_test_info()->name());
|
||||||
|
std::filesystem::create_directories(root / "res" / "maps" / "testzone");
|
||||||
|
std::ofstream(root / "res" / "CDServer.sqlite").close(); // marks an unpacked client
|
||||||
|
Game::assetManager = new AssetManager(root);
|
||||||
|
|
||||||
|
CDZoneTable zone{};
|
||||||
|
zone.zoneID = TEST_MAP;
|
||||||
|
zone.zoneName = "testzone/test.luz";
|
||||||
|
CDZoneTableTable::entries[TEST_MAP] = zone;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TearDown() override {
|
||||||
|
CDZoneTableTable::entries.erase(TEST_MAP);
|
||||||
|
delete Game::assetManager;
|
||||||
|
Game::assetManager = nullptr;
|
||||||
|
std::filesystem::remove_all(root);
|
||||||
|
TearDownDependencies();
|
||||||
|
}
|
||||||
|
|
||||||
|
const Zone* LoadZoneWithPaths(uint32_t pathCount, const std::string& paths) const {
|
||||||
|
std::ostringstream stream;
|
||||||
|
Write<uint32_t>(stream, 41); // version
|
||||||
|
Write<uint32_t>(stream, 1); // revision
|
||||||
|
Write<uint32_t>(stream, TEST_MAP);
|
||||||
|
Write(stream, NiPoint3(0.0f, 0.0f, 0.0f)); // spawn position
|
||||||
|
for (const float value : { 1.0f, 0.0f, 0.0f, 0.0f }) Write(stream, value); // spawn rotation
|
||||||
|
Write<uint32_t>(stream, 0); // scenes
|
||||||
|
Write<uint8_t>(stream, 0); // boundary lines
|
||||||
|
WriteString8(stream, "test.raw");
|
||||||
|
WriteString8(stream, "Test Zone");
|
||||||
|
WriteString8(stream, "A zone for tests");
|
||||||
|
Write<uint32_t>(stream, 0); // transitions
|
||||||
|
Write<uint32_t>(stream, static_cast<uint32_t>(8 + paths.size())); // path data length
|
||||||
|
Write<uint32_t>(stream, 1); // path chunk version
|
||||||
|
Write(stream, pathCount);
|
||||||
|
stream.write(paths.data(), paths.size());
|
||||||
|
const auto data = stream.str();
|
||||||
|
std::ofstream(root / "res" / "maps" / "testzone" / "test.luz", std::ios::binary).write(data.data(), data.size());
|
||||||
|
|
||||||
|
Game::zoneManager->LoadZone(LWOZONEID(TEST_MAP, 0, 0));
|
||||||
|
Zone* zone = Game::zoneManager->GetZoneMut();
|
||||||
|
zone->LoadZoneIntoMemory();
|
||||||
|
return zone;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Before version 3 a path names its type and every waypoint has the moving platform values and a command list
|
||||||
|
TEST_F(ZonePathTest, ReadsVersion2Paths) {
|
||||||
|
std::ostringstream paths;
|
||||||
|
Write<uint32_t>(paths, 2);
|
||||||
|
WriteWString8(paths, "Lava");
|
||||||
|
WriteWString8(paths, "npc");
|
||||||
|
Write<uint32_t>(paths, 0); // flags
|
||||||
|
Write<uint32_t>(paths, 0); // behavior
|
||||||
|
Write<uint32_t>(paths, 2);
|
||||||
|
WriteOldWaypoint(paths, 1.0f);
|
||||||
|
WriteOldWaypoint(paths, 2.0f);
|
||||||
|
Write<uint32_t>(paths, 2);
|
||||||
|
WriteWString8(paths, "plat");
|
||||||
|
WriteWString8(paths, "platform");
|
||||||
|
Write<uint32_t>(paths, 0);
|
||||||
|
Write<uint32_t>(paths, 0);
|
||||||
|
Write<uint32_t>(paths, 1);
|
||||||
|
WriteOldWaypoint(paths, 5.0f);
|
||||||
|
const Zone* zone = LoadZoneWithPaths(2, paths.str());
|
||||||
|
|
||||||
|
const Path* lava = zone->GetPath("Lava");
|
||||||
|
ASSERT_NE(lava, nullptr);
|
||||||
|
EXPECT_EQ(lava->pathType, PathType::Movement);
|
||||||
|
ASSERT_EQ(lava->pathWaypoints.size(), 2);
|
||||||
|
EXPECT_FLOAT_EQ(lava->pathWaypoints[1].position.x, 2.0f);
|
||||||
|
EXPECT_FLOAT_EQ(lava->pathWaypoints[1].speed, 3.0f);
|
||||||
|
ASSERT_EQ(lava->pathWaypoints[1].commands.size(), 1);
|
||||||
|
EXPECT_EQ(lava->pathWaypoints[1].commands[0].command, eWaypointCommandType::DELAY);
|
||||||
|
|
||||||
|
const Path* platform = zone->GetPath("plat");
|
||||||
|
ASSERT_NE(platform, nullptr);
|
||||||
|
EXPECT_EQ(platform->pathType, PathType::MovingPlatform);
|
||||||
|
ASSERT_EQ(platform->pathWaypoints.size(), 1);
|
||||||
|
EXPECT_FLOAT_EQ(platform->pathWaypoints[0].movingPlatform.wait, 4.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spawner and camera paths have no header before version 4; the spawner's network flag starts at version 9
|
||||||
|
TEST_F(ZonePathTest, SpawnerAndCameraHeadersStartAtVersion4) {
|
||||||
|
std::ostringstream paths;
|
||||||
|
WritePathHeader(paths, 3, "spawn3", PathType::Spawner);
|
||||||
|
Write<uint32_t>(paths, 1);
|
||||||
|
Write(paths, NiPoint3(7.0f, 0.0f, 0.0f));
|
||||||
|
for (const float value : { 1.0f, 0.0f, 0.0f, 0.0f }) Write(paths, value);
|
||||||
|
Write<uint32_t>(paths, 0); // config
|
||||||
|
WritePathHeader(paths, 8, "spawn8", PathType::Spawner);
|
||||||
|
Write<uint32_t>(paths, 1234); // LOT
|
||||||
|
Write<uint32_t>(paths, 10); // respawn time
|
||||||
|
Write<int32_t>(paths, 5); // max to spawn
|
||||||
|
Write<uint32_t>(paths, 2); // amount maintained
|
||||||
|
Write<uint64_t>(paths, 99); // spawner object ID
|
||||||
|
Write<uint32_t>(paths, 0); // no waypoints
|
||||||
|
WritePathHeader(paths, 3, "cam3", PathType::Camera);
|
||||||
|
Write<uint32_t>(paths, 0);
|
||||||
|
const Zone* zone = LoadZoneWithPaths(3, paths.str());
|
||||||
|
|
||||||
|
const Path* spawn3 = zone->GetPath("spawn3");
|
||||||
|
ASSERT_NE(spawn3, nullptr);
|
||||||
|
EXPECT_EQ(spawn3->spawner.spawnedLOT, 0);
|
||||||
|
ASSERT_EQ(spawn3->pathWaypoints.size(), 1);
|
||||||
|
EXPECT_FLOAT_EQ(spawn3->pathWaypoints[0].position.x, 7.0f);
|
||||||
|
|
||||||
|
const Path* spawn8 = zone->GetPath("spawn8");
|
||||||
|
ASSERT_NE(spawn8, nullptr);
|
||||||
|
EXPECT_EQ(spawn8->spawner.spawnedLOT, 1234);
|
||||||
|
EXPECT_EQ(spawn8->spawner.spawnerObjID, 99);
|
||||||
|
|
||||||
|
const Path* cam3 = zone->GetPath("cam3");
|
||||||
|
ASSERT_NE(cam3, nullptr);
|
||||||
|
EXPECT_TRUE(cam3->camera.nextPath.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Before version 11 a camera waypoint's field of view is 1.25 times larger
|
||||||
|
TEST_F(ZonePathTest, OldCameraFieldOfViewIsScaled) {
|
||||||
|
std::ostringstream paths;
|
||||||
|
for (const auto& [version, name] : { std::pair<uint32_t, std::string>{ 10, "cam10" }, { 11, "cam11" } }) {
|
||||||
|
WritePathHeader(paths, version, name, PathType::Camera);
|
||||||
|
WriteWString8(paths, "next");
|
||||||
|
Write<uint32_t>(paths, 1);
|
||||||
|
Write(paths, NiPoint3(9.0f, 0.0f, 0.0f));
|
||||||
|
for (const float value : { 1.0f, 0.0f, 0.0f, 0.0f }) Write(paths, value);
|
||||||
|
for (const float value : { 1.0f, 50.0f, 0.1f, 0.2f, 0.3f }) Write(paths, value); // time, fov, tension, continuity, bias
|
||||||
|
}
|
||||||
|
const Zone* zone = LoadZoneWithPaths(2, paths.str());
|
||||||
|
|
||||||
|
const Path* cam10 = zone->GetPath("cam10");
|
||||||
|
ASSERT_NE(cam10, nullptr);
|
||||||
|
EXPECT_EQ(cam10->camera.nextPath, "next");
|
||||||
|
ASSERT_EQ(cam10->pathWaypoints.size(), 1);
|
||||||
|
EXPECT_FLOAT_EQ(cam10->pathWaypoints[0].camera.fov, 40.0f);
|
||||||
|
EXPECT_FLOAT_EQ(cam10->pathWaypoints[0].camera.bias, 0.3f);
|
||||||
|
const Path* cam11 = zone->GetPath("cam11");
|
||||||
|
ASSERT_NE(cam11, nullptr);
|
||||||
|
EXPECT_FLOAT_EQ(cam11->pathWaypoints[0].camera.fov, 50.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Like the client, a path it can't read leaves the zone with no paths
|
||||||
|
TEST_F(ZonePathTest, UnreadablePathLeavesNoPaths) {
|
||||||
|
for (const uint32_t badVersion : { 19u, 25u }) {
|
||||||
|
std::ostringstream paths;
|
||||||
|
WritePathHeader(paths, 18, "ok", PathType::Movement);
|
||||||
|
Write<uint32_t>(paths, 0); // no waypoints
|
||||||
|
Write(paths, badVersion);
|
||||||
|
const Zone* zone = LoadZoneWithPaths(2, paths.str());
|
||||||
|
EXPECT_EQ(zone->GetPath("ok"), nullptr) << badVersion;
|
||||||
|
EXPECT_EQ(zone->GetZoneName(), "Test Zone") << badVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostringstream tooManyCommands;
|
||||||
|
WritePathHeader(tooManyCommands, 18, "commands", PathType::Movement);
|
||||||
|
Write<uint32_t>(tooManyCommands, 1);
|
||||||
|
Write(tooManyCommands, NiPoint3(0.0f, 0.0f, 0.0f));
|
||||||
|
Write<uint32_t>(tooManyCommands, 100);
|
||||||
|
EXPECT_EQ(LoadZoneWithPaths(1, tooManyCommands.str())->GetPath("commands"), nullptr);
|
||||||
|
|
||||||
|
std::ostringstream tooManyWaypoints;
|
||||||
|
WritePathHeader(tooManyWaypoints, 18, "waypoints", PathType::Movement);
|
||||||
|
Write<uint32_t>(tooManyWaypoints, 10000);
|
||||||
|
EXPECT_EQ(LoadZoneWithPaths(1, tooManyWaypoints.str())->GetPath("waypoints"), nullptr);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user