mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-06-25 16:14:20 +00:00
Replace old dNavigation/dTerrain raw parser with new Raw module in dZoneManager. Parse heightmaps, color maps, and scene maps from .raw files to determine which scene a position belongs to. Build scene adjacency graph from terrain data and scene transitions. Adds NiColor type, SceneColor lookup table, eSceneType enum, terrain mesh generation with OBJ export, and debug slash commands for scene visualization. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
549 lines
20 KiB
C++
549 lines
20 KiB
C++
#include "Zone.h"
|
||
#include "Level.h"
|
||
#include <fstream>
|
||
#include <sstream>
|
||
#include <ranges>
|
||
#include "Game.h"
|
||
#include "Logger.h"
|
||
#include "GeneralUtils.h"
|
||
#include "BinaryIO.h"
|
||
#include "LUTriggers.h"
|
||
#include "dConfig.h"
|
||
|
||
#include "AssetManager.h"
|
||
#include "CDClientManager.h"
|
||
#include "CDZoneTableTable.h"
|
||
#include "Spawner.h"
|
||
#include "dZoneManager.h"
|
||
#include "dpWorld.h"
|
||
|
||
#include "eTriggerCommandType.h"
|
||
#include "eTriggerEventType.h"
|
||
#include "eWaypointCommandType.h"
|
||
#include "dNavMesh.h"
|
||
#include "Raw.h"
|
||
|
||
Zone::Zone(const LWOZONEID zoneID) :
|
||
m_ZoneID(zoneID) {
|
||
m_NumberOfObjectsLoaded = 0;
|
||
m_NumberOfSceneTransitionsLoaded = 0;
|
||
m_CheckSum = 0;
|
||
m_WorldID = 0;
|
||
m_SceneCount = 0;
|
||
}
|
||
|
||
Zone::~Zone() {
|
||
LOG("Destroying zone %i", m_ZoneID.GetMapID());
|
||
}
|
||
|
||
void Zone::Initalize() {
|
||
LoadZoneIntoMemory();
|
||
LoadLevelsIntoMemory();
|
||
m_CheckSum = CalculateChecksum();
|
||
}
|
||
|
||
void Zone::LoadZoneIntoMemory() {
|
||
m_ZoneFilePath = GetFilePathForZoneID();
|
||
m_ZonePath = m_ZoneFilePath.substr(0, m_ZoneFilePath.rfind('/') + 1);
|
||
if (m_ZoneFilePath == "ERR") return;
|
||
|
||
auto file = Game::assetManager->GetFile(m_ZoneFilePath.c_str());
|
||
|
||
if (!file) {
|
||
LOG("Failed to load %s", m_ZoneFilePath.c_str());
|
||
throw std::runtime_error("Aborting Zone loading due to no Zone File.");
|
||
}
|
||
|
||
if (file) {
|
||
BinaryIO::BinaryRead(file, m_FileFormatVersion);
|
||
|
||
uint32_t mapRevision = 0;
|
||
if (m_FileFormatVersion >= Zone::FileFormatVersion::Alpha) BinaryIO::BinaryRead(file, mapRevision);
|
||
|
||
BinaryIO::BinaryRead(file, m_WorldID);
|
||
if (static_cast<LWOMAPID>(m_WorldID) != m_ZoneID.GetMapID()) LOG("WorldID: %i doesn't match MapID %i! Is this intended?", m_WorldID, m_ZoneID.GetMapID());
|
||
|
||
AddRevision(LWOSCENEID_INVALID, mapRevision);
|
||
|
||
if (m_FileFormatVersion >= Zone::FileFormatVersion::Beta) {
|
||
BinaryIO::BinaryRead(file, m_Spawnpoint);
|
||
BinaryIO::BinaryRead(file, m_SpawnpointRotation);
|
||
}
|
||
|
||
if (m_FileFormatVersion <= Zone::FileFormatVersion::LateAlpha) {
|
||
uint8_t sceneCount;
|
||
BinaryIO::BinaryRead(file, sceneCount);
|
||
m_SceneCount = sceneCount;
|
||
} else BinaryIO::BinaryRead(file, m_SceneCount);
|
||
|
||
for (uint32_t i = 0; i < m_SceneCount; ++i) {
|
||
LoadScene(file);
|
||
}
|
||
|
||
// Zone boundary lines — skip past them for correct file positioning
|
||
uint8_t numBoundaries = 0;
|
||
BinaryIO::BinaryRead(file, numBoundaries);
|
||
for (uint8_t i = 0; i < numBoundaries; ++i) {
|
||
NiPoint3 normal, point, spawnLocation;
|
||
uint32_t packed, destSceneID;
|
||
BinaryIO::BinaryRead(file, normal);
|
||
BinaryIO::BinaryRead(file, point);
|
||
BinaryIO::BinaryRead(file, packed);
|
||
BinaryIO::BinaryRead(file, destSceneID);
|
||
BinaryIO::BinaryRead(file, spawnLocation);
|
||
}
|
||
|
||
BinaryIO::ReadString<uint8_t>(file, m_ZoneRawPath, BinaryIO::ReadType::String);
|
||
BinaryIO::ReadString<uint8_t>(file, m_ZoneName, BinaryIO::ReadType::String);
|
||
BinaryIO::ReadString<uint8_t>(file, m_ZoneDesc, BinaryIO::ReadType::String);
|
||
|
||
auto zoneFolderPath = m_ZoneFilePath.substr(0, m_ZoneFilePath.rfind('/') + 1);
|
||
if (!Game::assetManager->HasFile(zoneFolderPath + m_ZoneRawPath)) {
|
||
LOG("Failed to find %s", (zoneFolderPath + m_ZoneRawPath).c_str());
|
||
throw std::runtime_error("Aborting Zone loading due to no Zone Raw File.");
|
||
}
|
||
|
||
auto rawFile = Game::assetManager->GetFile(zoneFolderPath + m_ZoneRawPath);
|
||
if (!Raw::ReadRaw(rawFile, m_Raw)) {
|
||
LOG("Failed to parse %s", (zoneFolderPath + m_ZoneRawPath).c_str());
|
||
throw std::runtime_error("Aborting Zone loading due to invalid Raw File.");
|
||
}
|
||
LOG("Loaded Raw Terrain with %u chunks", m_Raw.numChunks);
|
||
|
||
|
||
// Optionally export terrain mesh to OBJ for debugging/visualization
|
||
if (Game::config->GetValue("export_terrain_to_obj") == "1") {
|
||
|
||
// Generate terrain mesh
|
||
Raw::GenerateTerrainMesh(m_Raw, m_TerrainMesh);
|
||
LOG("Generated terrain mesh with %zu vertices and %zu triangles", m_TerrainMesh.vertices.size(), m_TerrainMesh.triangles.size() / 3);
|
||
|
||
// Write to OBJ
|
||
std::string objFileName = "terrain_" + std::to_string(m_ZoneID.GetMapID()) + ".obj";
|
||
if (Raw::WriteTerrainMeshToOBJ(m_TerrainMesh, objFileName)) {
|
||
LOG("Exported terrain mesh to %s", objFileName.c_str());
|
||
}
|
||
}
|
||
|
||
if (m_FileFormatVersion >= Zone::FileFormatVersion::PreAlpha) {
|
||
BinaryIO::BinaryRead(file, m_NumberOfSceneTransitionsLoaded);
|
||
for (uint32_t i = 0; i < m_NumberOfSceneTransitionsLoaded; ++i) {
|
||
LoadSceneTransition(file);
|
||
}
|
||
}
|
||
|
||
if (m_FileFormatVersion >= Zone::FileFormatVersion::EarlyAlpha) {
|
||
BinaryIO::BinaryRead(file, m_PathDataLength);
|
||
BinaryIO::BinaryRead(file, m_PathChunkVersion); // always should be 1
|
||
|
||
uint32_t pathCount;
|
||
BinaryIO::BinaryRead(file, pathCount);
|
||
|
||
m_Paths.reserve(pathCount);
|
||
for (uint32_t i = 0; i < pathCount; ++i) LoadPath(file);
|
||
|
||
for (const Path& path : m_Paths) {
|
||
if (path.pathType != PathType::Spawner) continue;
|
||
SpawnerInfo info{};
|
||
for (size_t i = 0; i < path.pathWaypoints.size(); i++) {
|
||
const auto& waypoint = path.pathWaypoints[i];
|
||
SpawnerNode* node = new SpawnerNode();
|
||
node->position = waypoint.position;
|
||
node->rotation = waypoint.rotation;
|
||
node->nodeID = 0;
|
||
node->config = path.pathWaypoints[0].config;
|
||
// All spawner waypoints get the config data of the first waypoint, but then we
|
||
// overwrite settings on this waypoint if we have another one defined of the same name
|
||
if (i != 0) {
|
||
for (const auto& [key, value] : waypoint.config) {
|
||
node->config.ParseInsert(value->GetString());
|
||
}
|
||
}
|
||
|
||
for (const auto& data : waypoint.config | std::views::values) {
|
||
if (!data) continue;
|
||
|
||
if (data->GetKey() == u"spawner_node_id") {
|
||
node->nodeID = GeneralUtils::TryParse(data->GetValueAsString(), 0);
|
||
} else if (data->GetKey() == u"spawner_max_per_node") {
|
||
node->nodeMax = GeneralUtils::TryParse(data->GetValueAsString(), 0);
|
||
} else if (data->GetKey() == u"groupID") { // Load object group
|
||
info.groups = GeneralUtils::SplitString(data->GetValueAsString(), ';');
|
||
if (info.groups.back().empty()) info.groups.erase(info.groups.end() - 1);
|
||
} else if (data->GetKey() == u"grpNameQBShowBricks") {
|
||
info.grpNameQBShowBricks = data->GetValueAsString();
|
||
} else if (data->GetKey() == u"spawner_name") {
|
||
info.name = data->GetValueAsString();
|
||
} else if (data->GetKey() == u"weight") {
|
||
node->weight = GeneralUtils::TryParse(data->GetValueAsString(), 1);
|
||
if (node->weight <= 0) {
|
||
LOG("Found a spawner with a weight of <= 0, is this intentional? %s:%i", info.name.c_str(), node->nodeID);
|
||
node->weight = 1;
|
||
}
|
||
}
|
||
}
|
||
|
||
info.nodes.push_back(node);
|
||
}
|
||
info.templateID = path.spawner.spawnedLOT;
|
||
info.spawnerID = path.spawner.spawnerObjID;
|
||
info.respawnTime = path.spawner.respawnTime;
|
||
info.amountMaintained = path.spawner.amountMaintained;
|
||
info.maxToSpawn = path.spawner.maxToSpawn;
|
||
info.activeOnLoad = path.spawner.spawnerNetActive;
|
||
info.isNetwork = true;
|
||
Spawner* spawner = new Spawner(info);
|
||
Game::zoneManager->AddSpawner(info.spawnerID, spawner);
|
||
}
|
||
}
|
||
} else {
|
||
LOG("Failed to open: %s", m_ZoneFilePath.c_str());
|
||
}
|
||
|
||
m_ZonePath = m_ZoneFilePath.substr(0, m_ZoneFilePath.rfind('/') + 1);
|
||
}
|
||
|
||
std::string Zone::GetFilePathForZoneID() const {
|
||
//We're gonna go ahead and presume we've got the db loaded already:
|
||
const CDZoneTable* zone = CDZoneTableTable::Query(this->GetZoneID().GetMapID());
|
||
std::string toReturn("ERR");
|
||
if (zone != nullptr) {
|
||
toReturn = "maps/" + zone->zoneName;
|
||
std::transform(toReturn.begin(), toReturn.end(), toReturn.begin(), ::tolower);
|
||
|
||
/* Normalize to one slash type */
|
||
std::ranges::replace(toReturn, '\\', '/');
|
||
}
|
||
|
||
return toReturn;
|
||
}
|
||
|
||
//Based off code from: https://www.liquisearch.com/fletchers_checksum/implementation/optimizations
|
||
uint32_t Zone::CalculateChecksum() const {
|
||
uint32_t sum1 = 0xffff;
|
||
uint32_t sum2 = 0xffff;
|
||
|
||
for (const auto& [scene, sceneRevision] : m_MapRevisions) {
|
||
uint32_t sceneID = scene.GetSceneID();
|
||
sum2 += sum1 += (sceneID >> 16);
|
||
sum2 += sum1 += (sceneID & 0xffff);
|
||
|
||
uint32_t layerID = scene.GetLayerID();
|
||
sum2 += sum1 += (layerID >> 16);
|
||
sum2 += sum1 += (layerID & 0xffff);
|
||
|
||
uint32_t revision = sceneRevision;
|
||
sum2 += sum1 += (revision >> 16);
|
||
sum2 += sum1 += (revision & 0xffff);
|
||
}
|
||
|
||
sum1 = (sum1 & 0xffff) + (sum1 >> 16);
|
||
sum2 = (sum2 & 0xffff) + (sum2 >> 16);
|
||
|
||
return sum2 << 16 | sum1;
|
||
}
|
||
|
||
void Zone::LoadLevelsIntoMemory() {
|
||
for (auto& [sceneID, scene] : m_Scenes) {
|
||
if (scene.level) continue;
|
||
scene.level = std::make_unique<Level>(this, m_ZonePath + scene.filename);
|
||
|
||
if (scene.level->m_ChunkHeaders.empty()) continue;
|
||
|
||
scene.level->m_ChunkHeaders.begin()->second.lwoSceneID = sceneID;
|
||
AddRevision(scene.level->m_ChunkHeaders.begin()->second.lwoSceneID, scene.level->m_ChunkHeaders.begin()->second.fileInfo.revision);
|
||
}
|
||
}
|
||
|
||
void Zone::AddRevision(LWOSCENEID sceneID, uint32_t revision) {
|
||
if (m_MapRevisions.find(sceneID) == m_MapRevisions.end()) {
|
||
m_MapRevisions.insert(std::make_pair(sceneID, revision));
|
||
}
|
||
}
|
||
|
||
void Zone::LoadScene(std::istream& file) {
|
||
SceneRef scene;
|
||
scene.level = nullptr;
|
||
LWOSCENEID lwoSceneID(LWOZONEID_INVALID, 0);
|
||
|
||
BinaryIO::ReadString<uint8_t>(file, scene.filename, BinaryIO::ReadType::String);
|
||
|
||
std::string luTriggersPath = scene.filename.substr(0, scene.filename.size() - 4) + ".lutriggers";
|
||
if (Game::assetManager->HasFile((m_ZonePath + luTriggersPath).c_str())) LoadLUTriggers(luTriggersPath, scene);
|
||
|
||
if (m_FileFormatVersion >= Zone::FileFormatVersion::LatePreAlpha || m_FileFormatVersion < Zone::FileFormatVersion::PrePreAlpha) {
|
||
BinaryIO::BinaryRead(file, scene.id);
|
||
lwoSceneID.SetSceneID(scene.id);
|
||
}
|
||
if (m_FileFormatVersion >= Zone::FileFormatVersion::LatePreAlpha) {
|
||
BinaryIO::BinaryRead(file, scene.sceneType);
|
||
lwoSceneID.SetLayerID(static_cast<uint32_t>(scene.sceneType));
|
||
|
||
|
||
BinaryIO::ReadString<uint8_t>(file, scene.name, BinaryIO::ReadType::String);
|
||
}
|
||
|
||
if (m_FileFormatVersion == Zone::FileFormatVersion::LatePreAlpha) {
|
||
BinaryIO::BinaryRead(file, scene.scenePosition);
|
||
BinaryIO::BinaryRead(file, scene.sceneRadius);
|
||
}
|
||
|
||
if (m_FileFormatVersion >= Zone::FileFormatVersion::LatePreAlpha) {
|
||
uint8_t r, b, g;
|
||
BinaryIO::BinaryRead(file, r);
|
||
BinaryIO::BinaryRead(file, b);
|
||
BinaryIO::BinaryRead(file, g);
|
||
scene.color = NiColor(r / 255.0f, g / 255.0f, b / 255.0f);
|
||
}
|
||
|
||
m_Scenes[lwoSceneID] = std::move(scene);
|
||
}
|
||
|
||
void Zone::LoadLUTriggers(std::string triggerFile, SceneRef& scene) {
|
||
auto file = Game::assetManager->GetFile((m_ZonePath + triggerFile).c_str());
|
||
|
||
std::stringstream data;
|
||
data << file.rdbuf();
|
||
|
||
data.seekg(0, std::ios::end);
|
||
int32_t size = data.tellg();
|
||
data.seekg(0, std::ios::beg);
|
||
|
||
if (size == 0) return;
|
||
|
||
tinyxml2::XMLDocument doc;
|
||
|
||
if (doc.Parse(data.str().c_str(), size) != tinyxml2::XML_SUCCESS) {
|
||
LOG("Failed to load LUTriggers from file %s", triggerFile.c_str());
|
||
return;
|
||
}
|
||
|
||
auto* triggers = doc.FirstChildElement("triggers");
|
||
if (!triggers) return;
|
||
|
||
auto* currentTrigger = triggers->FirstChildElement("trigger");
|
||
while (currentTrigger) {
|
||
LUTriggers::Trigger* newTrigger = new LUTriggers::Trigger();
|
||
currentTrigger->QueryAttribute("enabled", &newTrigger->enabled);
|
||
currentTrigger->QueryAttribute("id", &newTrigger->id);
|
||
|
||
auto* currentEvent = currentTrigger->FirstChildElement("event");
|
||
while (currentEvent) {
|
||
LUTriggers::Event* newEvent = new LUTriggers::Event();
|
||
newEvent->id = TriggerEventType::StringToTriggerEventType(currentEvent->Attribute("id"));
|
||
auto* currentCommand = currentEvent->FirstChildElement("command");
|
||
while (currentCommand) {
|
||
LUTriggers::Command* newCommand = new LUTriggers::Command();
|
||
newCommand->id = TriggerCommandType::StringToTriggerCommandType(currentCommand->Attribute("id"));
|
||
newCommand->target = currentCommand->Attribute("target");
|
||
if (currentCommand->Attribute("targetName")) {
|
||
newCommand->targetName = currentCommand->Attribute("targetName");
|
||
}
|
||
if (currentCommand->Attribute("args")) {
|
||
newCommand->args = currentCommand->Attribute("args");
|
||
}
|
||
|
||
newEvent->commands.push_back(newCommand);
|
||
currentCommand = currentCommand->NextSiblingElement("command");
|
||
}
|
||
newTrigger->events.push_back(newEvent);
|
||
currentEvent = currentEvent->NextSiblingElement("event");
|
||
}
|
||
currentTrigger = currentTrigger->NextSiblingElement("trigger");
|
||
scene.triggers.insert(std::make_pair(newTrigger->id, newTrigger));
|
||
}
|
||
}
|
||
|
||
LUTriggers::Trigger* Zone::GetTrigger(uint32_t sceneID, uint32_t triggerID) const {
|
||
auto scene = m_Scenes.find(sceneID);
|
||
if (scene == m_Scenes.end()) return nullptr;
|
||
|
||
auto trigger = scene->second.triggers.find(triggerID);
|
||
if (trigger == scene->second.triggers.end()) return nullptr;
|
||
|
||
return trigger->second;
|
||
}
|
||
|
||
const Path* Zone::GetPath(std::string name) const {
|
||
for (const auto& path : m_Paths) {
|
||
if (name == path.pathName) {
|
||
return &path;
|
||
}
|
||
}
|
||
|
||
return nullptr;
|
||
}
|
||
|
||
void Zone::LoadSceneTransition(std::istream& file) {
|
||
SceneTransition sceneTrans;
|
||
if (m_FileFormatVersion < Zone::FileFormatVersion::Auramar) {
|
||
BinaryIO::ReadString<uint8_t>(file, sceneTrans.name, BinaryIO::ReadType::String);
|
||
BinaryIO::BinaryRead(file, sceneTrans.width);
|
||
}
|
||
|
||
//BR<42>THER MAY I HAVE SOME L<><4C>PS?
|
||
uint8_t loops = (m_FileFormatVersion <= Zone::FileFormatVersion::LatePreAlpha || m_FileFormatVersion >= Zone::FileFormatVersion::Launch) ? 2 : 5;
|
||
|
||
sceneTrans.points.reserve(loops);
|
||
for (uint8_t i = 0; i < loops; ++i) {
|
||
sceneTrans.points.push_back(LoadSceneTransitionInfo(file));
|
||
}
|
||
|
||
m_SceneTransitions.push_back(sceneTrans);
|
||
}
|
||
|
||
SceneTransitionInfo Zone::LoadSceneTransitionInfo(std::istream& file) {
|
||
SceneTransitionInfo info;
|
||
uint32_t sceneID, layerID;
|
||
BinaryIO::BinaryRead(file, sceneID);
|
||
BinaryIO::BinaryRead(file, layerID);
|
||
info.sceneID = LWOSCENEID(static_cast<int32_t>(sceneID), layerID);
|
||
BinaryIO::BinaryRead(file, info.position);
|
||
return info;
|
||
}
|
||
|
||
void Zone::LoadPath(std::istream& file) {
|
||
Path path = Path();
|
||
|
||
BinaryIO::BinaryRead(file, path.pathVersion);
|
||
|
||
BinaryIO::ReadString<uint8_t>(file, path.pathName, BinaryIO::ReadType::WideString);
|
||
|
||
BinaryIO::BinaryRead(file, path.pathType);
|
||
BinaryIO::BinaryRead(file, path.flags);
|
||
BinaryIO::BinaryRead(file, path.pathBehavior);
|
||
|
||
if (path.pathType == PathType::MovingPlatform) {
|
||
if (path.pathVersion >= 18) {
|
||
BinaryIO::BinaryRead(file, path.movingPlatform.timeBasedMovement);
|
||
} else if (path.pathVersion >= 13) {
|
||
BinaryIO::ReadString<uint8_t>(file, path.movingPlatform.platformTravelSound, BinaryIO::ReadType::WideString);
|
||
}
|
||
} else if (path.pathType == PathType::Property) {
|
||
BinaryIO::BinaryRead(file, path.property.pathType);
|
||
BinaryIO::BinaryRead(file, path.property.price);
|
||
BinaryIO::BinaryRead(file, path.property.rentalTime);
|
||
BinaryIO::BinaryRead(file, path.property.associatedZone);
|
||
|
||
if (path.pathVersion >= 5) {
|
||
BinaryIO::ReadString<uint8_t>(file, path.property.displayName, BinaryIO::ReadType::WideString);
|
||
BinaryIO::ReadString<uint32_t>(file, path.property.displayDesc, BinaryIO::ReadType::WideString);
|
||
}
|
||
|
||
if (path.pathVersion >= 6) BinaryIO::BinaryRead(file, path.property.type);
|
||
|
||
if (path.pathVersion >= 7) {
|
||
BinaryIO::BinaryRead(file, path.property.cloneLimit);
|
||
BinaryIO::BinaryRead(file, path.property.repMultiplier);
|
||
BinaryIO::BinaryRead(file, path.property.rentalPeriod);
|
||
}
|
||
|
||
if (path.pathVersion >= 8) {
|
||
BinaryIO::BinaryRead(file, path.property.achievementRequired);
|
||
BinaryIO::BinaryRead(file, path.property.playerZoneCoords);
|
||
BinaryIO::BinaryRead(file, path.property.maxBuildHeight);
|
||
}
|
||
} else if (path.pathType == PathType::Camera) {
|
||
BinaryIO::ReadString<uint8_t>(file, path.camera.nextPath, BinaryIO::ReadType::WideString);
|
||
if (path.pathVersion >= 14) {
|
||
BinaryIO::BinaryRead(file, path.camera.rotatePlayer);
|
||
|
||
}
|
||
} else if (path.pathType == PathType::Spawner) {
|
||
BinaryIO::BinaryRead(file, path.spawner.spawnedLOT);
|
||
BinaryIO::BinaryRead(file, path.spawner.respawnTime);
|
||
BinaryIO::BinaryRead(file, path.spawner.maxToSpawn);
|
||
BinaryIO::BinaryRead(file, path.spawner.amountMaintained);
|
||
BinaryIO::BinaryRead(file, path.spawner.spawnerObjID);
|
||
BinaryIO::BinaryRead(file, path.spawner.spawnerNetActive);
|
||
}
|
||
|
||
// Read waypoints
|
||
|
||
BinaryIO::BinaryRead(file, path.waypointCount);
|
||
path.pathWaypoints.reserve(path.waypointCount);
|
||
for (uint32_t i = 0; i < path.waypointCount; ++i) {
|
||
PathWaypoint waypoint = PathWaypoint();
|
||
|
||
BinaryIO::BinaryRead(file, waypoint.position.x);
|
||
BinaryIO::BinaryRead(file, waypoint.position.y);
|
||
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) {
|
||
BinaryIO::BinaryRead(file, waypoint.rotation.w);
|
||
BinaryIO::BinaryRead(file, waypoint.rotation.x);
|
||
BinaryIO::BinaryRead(file, waypoint.rotation.y);
|
||
BinaryIO::BinaryRead(file, waypoint.rotation.z);
|
||
}
|
||
|
||
if (path.pathType == PathType::MovingPlatform) {
|
||
BinaryIO::BinaryRead(file, waypoint.movingPlatform.lockPlayer);
|
||
BinaryIO::BinaryRead(file, waypoint.speed);
|
||
BinaryIO::BinaryRead(file, waypoint.movingPlatform.wait);
|
||
if (path.pathVersion >= 13) {
|
||
BinaryIO::ReadString<uint8_t>(file, waypoint.movingPlatform.departSound, BinaryIO::ReadType::WideString);
|
||
BinaryIO::ReadString<uint8_t>(file, waypoint.movingPlatform.arriveSound, BinaryIO::ReadType::WideString);
|
||
}
|
||
} else if (path.pathType == PathType::Camera) {
|
||
BinaryIO::BinaryRead(file, waypoint.camera.time);
|
||
BinaryIO::BinaryRead(file, waypoint.camera.fov);
|
||
BinaryIO::BinaryRead(file, waypoint.camera.tension);
|
||
BinaryIO::BinaryRead(file, waypoint.camera.continuity);
|
||
BinaryIO::BinaryRead(file, waypoint.camera.bias);
|
||
} else if (path.pathType == PathType::Race) {
|
||
BinaryIO::BinaryRead(file, waypoint.racing.isResetNode);
|
||
BinaryIO::BinaryRead(file, waypoint.racing.isNonHorizontalCamera);
|
||
BinaryIO::BinaryRead(file, waypoint.racing.planeWidth);
|
||
BinaryIO::BinaryRead(file, waypoint.racing.planeHeight);
|
||
BinaryIO::BinaryRead(file, waypoint.racing.shortestDistanceToEnd);
|
||
} else if (path.pathType == PathType::Rail) {
|
||
if (path.pathVersion > 16) BinaryIO::BinaryRead(file, waypoint.speed);
|
||
}
|
||
|
||
// object LDF configs
|
||
if (path.pathType == PathType::Movement || path.pathType == PathType::Spawner || path.pathType == PathType::Rail) {
|
||
uint32_t count;
|
||
BinaryIO::BinaryRead(file, count);
|
||
for (uint32_t i = 0; i < count; ++i) {
|
||
std::string parameter;
|
||
BinaryIO::ReadString<uint8_t>(file, parameter, BinaryIO::ReadType::WideString);
|
||
|
||
std::string value;
|
||
BinaryIO::ReadString<uint8_t>(file, value, BinaryIO::ReadType::WideString);
|
||
|
||
if (path.pathType == PathType::Movement || path.pathType == PathType::Rail) {
|
||
// cause NetDevil puts spaces in things that don't need spaces
|
||
parameter.erase(std::remove_if(parameter.begin(), parameter.end(), ::isspace), parameter.end());
|
||
auto waypointCommand = WaypointCommandType::StringToWaypointCommandType(parameter);
|
||
if (waypointCommand == eWaypointCommandType::DELAY) value.erase(std::remove_if(value.begin(), value.end(), ::isspace), value.end());
|
||
if (waypointCommand != eWaypointCommandType::INVALID) {
|
||
auto& command = waypoint.commands.emplace_back();
|
||
command.command = waypointCommand;
|
||
command.data = value;
|
||
} else LOG("Tried to load invalid waypoint command '%s'", parameter.c_str());
|
||
} else {
|
||
waypoint.config.ParseInsert(parameter + "=" + value);
|
||
}
|
||
|
||
}
|
||
}
|
||
|
||
// We verify the waypoint heights against the navmesh because in many movement paths,
|
||
// the waypoint is located near 0 height,
|
||
if (path.pathType == PathType::Movement) {
|
||
if (dpWorld::IsLoaded()) {
|
||
// 2000 should be large enough for every world.
|
||
waypoint.position.y = dpWorld::GetNavMesh()->GetHeightAtPoint(waypoint.position, 2000.0f);
|
||
}
|
||
}
|
||
path.pathWaypoints.push_back(waypoint);
|
||
}
|
||
m_Paths.push_back(path);
|
||
}
|
||
|
||
const SceneRef* Zone::GetScene(LWOSCENEID sceneID) const {
|
||
auto it = m_Scenes.find(sceneID);
|
||
if (it != m_Scenes.end()) return &it->second;
|
||
return nullptr;
|
||
}
|