mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-28 07:27:14 +00:00
* feat: raw terrain parsing for scene data 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> * refactor: deduplicate terrain grid lookup logic, remove unnecessary cast Add IsValidForSceneLookup(), GetSceneIDAtGrid(), and GridToWorldPos() to Raw::Chunk. Use them in GenerateTerrainMesh, GetSceneIDFromPosition, SpawnScenePoints, and SpawnAllScenePoints instead of duplicated inline math. Remove unnecessary static_cast in LoadSceneTransitionInfo. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: add version < 32 support to raw terrain parser The parser was only correct for v32 files. For v<32: - colorMapResolution is width-1, not width - Color map reads width*width*4 bytes (BGRA per-pixel), not colorMapRes^2*4 - No DDS lightmap - No texture settings byte or blend DDS - Scene map: v31 reads (colorMapRes+1)^2 cells with border skip, v<31 skips 1 byte - No mesh data Verified against all 309 .raw files across multiple client versions. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: log and reject legacy raw terrain format, clean up ReadRaw API Log a clear message and throw if the zone uses a legacy raw format (LUZ version < 30) since no known maps use it. Remove ReadLegacyRaw and the mapVersion parameter from ReadRaw. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: revert v33 scene field renames to unknown1/unknown2 These fields are skipped by the client in every known build (ptr += 12 and ptr += 4). No client ever reads them into a struct. Keep the original unknown1/unknown2 names from main. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: load the zone without scene lookups when its raw terrain is missing or unreadable Log at debug when it parses; build the terrain mesh only for the OBJ export; read the zone name and description only for version 31 and later, as the client does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: look up the scene at a position as the client does, in constant time Chunk bounds span (width - 1) * scale; the nearest scene map cell is used and the far edge is kept one cell inside. Chunks are indexed by grid cell when they form a grid. Skipping reads instead of seeking, so a cut-off file can't seek past the end. Terrain mesh indices follow the vertex order. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat: scene point commands take a step and use brick LOTs from the BrickIDTable Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: rename NiColor::ToXYZ to Luminance Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: raw terrain reading, scene lookup, scene graph and scene commands Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: close the OBJ file before removing it so the test passes on Windows Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: keep the zone boundary lines and read the luz scene count as the client does Each boundary line's destination is read as a map ID and an instance ID into a LWOZONEID. The scene count is a byte before version 37 and a u32 from it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: name the fourth flair colour byte padding The client never sets or reads it, so it holds leftover memory. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: keep the scene map of version 31 and older raws These files have a (resolution + 1)^2 scene map, kept in sceneMapLegacy. Version 31 uses its first resolution rows and columns; older versions are all scene 0, as in the client. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor: simplify the raw heightmap size check The dimensions are capped at kMaxResolution, so the product can't overflow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: remove the scene point spawn commands and send /getadjacentscenes as one message The spawn commands were only for testing. /getscene and /getadjacentscenes stay. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
556 lines
20 KiB
C++
556 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);
|
||
}
|
||
|
||
// A byte before version 37 (LateAlpha), a u32 from it
|
||
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: a count byte, then the lines
|
||
uint8_t numBoundaries = 0;
|
||
BinaryIO::BinaryRead(file, numBoundaries);
|
||
m_Boundaries.resize(numBoundaries);
|
||
for (auto& boundary : m_Boundaries) {
|
||
LWOMAPID destMapID = 0;
|
||
LWOINSTANCEID destInstanceID = 0;
|
||
BinaryIO::BinaryRead(file, boundary.normal);
|
||
BinaryIO::BinaryRead(file, boundary.point);
|
||
BinaryIO::BinaryRead(file, destMapID);
|
||
BinaryIO::BinaryRead(file, destInstanceID);
|
||
boundary.destinationZone = LWOZONEID(destMapID, destInstanceID, 0);
|
||
BinaryIO::BinaryRead(file, boundary.destinationSceneID);
|
||
BinaryIO::BinaryRead(file, boundary.spawnLocation);
|
||
}
|
||
|
||
BinaryIO::ReadString<uint8_t>(file, m_ZoneRawPath, BinaryIO::ReadType::String);
|
||
if (m_FileFormatVersion > Zone::FileFormatVersion::PrePreAlpha) {
|
||
BinaryIO::ReadString<uint8_t>(file, m_ZoneName, BinaryIO::ReadType::String);
|
||
BinaryIO::ReadString<uint8_t>(file, m_ZoneDesc, BinaryIO::ReadType::String);
|
||
}
|
||
|
||
// The terrain gives the scene at a position. Like the client, a missing or unreadable .raw is logged
|
||
// and the zone loads without it.
|
||
const auto rawPath = m_ZoneFilePath.substr(0, m_ZoneFilePath.rfind('/') + 1) + m_ZoneRawPath;
|
||
if (m_FileFormatVersion < Zone::FileFormatVersion::PrePreAlpha) {
|
||
LOG("Zone %s uses the legacy raw terrain format (version %u), which is not supported; no scene lookups", m_ZoneFilePath.c_str(), static_cast<uint32_t>(m_FileFormatVersion));
|
||
} else if (!Game::assetManager->HasFile(rawPath)) {
|
||
LOG("Failed to find %s; no scene lookups", rawPath.c_str());
|
||
} else {
|
||
auto rawFile = Game::assetManager->GetFile(rawPath);
|
||
if (Raw::ReadRaw(rawFile, m_Raw)) {
|
||
LOG_DEBUG("Successfully parsed %s", rawPath.c_str());
|
||
} else {
|
||
LOG("Failed to parse %s; no scene lookups", rawPath.c_str());
|
||
m_Raw = Raw::Raw{};
|
||
}
|
||
}
|
||
|
||
// Optionally export terrain mesh to OBJ for debugging/visualization
|
||
if (Game::config->GetValue("export_terrain_to_obj") == "1") {
|
||
Raw::TerrainMesh terrainMesh;
|
||
Raw::GenerateTerrainMesh(m_Raw, terrainMesh);
|
||
LOG("Generated terrain mesh with %zu vertices and %zu triangles", terrainMesh.vertices.size(), terrainMesh.triangles.size() / 3);
|
||
|
||
const std::string objFileName = "terrain_" + std::to_string(m_ZoneID.GetMapID()) + ".obj";
|
||
if (Raw::WriteTerrainMeshToOBJ(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.unknown1);
|
||
BinaryIO::BinaryRead(file, scene.unknown2);
|
||
}
|
||
|
||
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(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;
|
||
}
|