feat: web dashboard and playground work

The NexusDashboard-parity dashboard (dDashboardServer) and everything built on it on the experimental branch:
accounts, characters, properties and moderation tools, permissions shared with in-game slash commands, economy
reports, World 3D and property 3D views with client scenery, scheduled events (features, vanity changes, live
events, announcements, restarts), vanity files and events, the CDClient browser, the message inspector with saved
captures, chat filter tools, community challenges, live ops, the AI moderator helper, and the server-side changes
they need.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-09-26 17:52:10 -05:00
parent 854d02f509
commit e213a7aeff
681 changed files with 88310 additions and 1321 deletions

View File

@@ -14,6 +14,7 @@
#include "CDFeatureGatingTable.h"
#include "CDClientManager.h"
#include "AssetManager.h"
#include "LevelFile.h"
#include "ClientVersion.h"
#include "dConfig.h"
#include <ranges>
@@ -28,7 +29,15 @@ Level::Level(Zone* parentZone, const std::string& filepath) {
return;
}
ReadChunks(stream);
// The file itself is read by LevelFile (dCommon), which the dashboard uses too
LevelFile levelFile;
levelFile.Read(stream);
for (const auto& [id, chunkHeader] : levelFile.chunkHeaders) {
Header header;
static_cast<LevelFile::ChunkHeader&>(header) = chunkHeader;
m_ChunkHeaders.insert(std::make_pair(id, header));
}
LoadSceneObjects(levelFile.objects);
}
void Level::MakeSpawner(const SceneObject& obj) {
@@ -100,123 +109,7 @@ void Level::MakeSpawner(const SceneObject& obj) {
Game::zoneManager->MakeSpawner(spawnInfo);
}
void Level::ReadChunks(std::istream& file) {
const uint32_t CHNK_HEADER = ('C' + ('H' << 8) + ('N' << 16) + ('K' << 24));
while (!file.eof()) {
uint32_t initPos = uint32_t(file.tellg());
uint32_t header = 0;
BinaryIO::BinaryRead(file, header);
if (header == CHNK_HEADER) { //Make sure we're reading a valid CHNK
Header header;
BinaryIO::BinaryRead(file, header.id);
BinaryIO::BinaryRead(file, header.chunkVersion);
BinaryIO::BinaryRead(file, header.chunkType);
BinaryIO::BinaryRead(file, header.size);
BinaryIO::BinaryRead(file, header.startPosition);
uint32_t target = initPos + header.size;
file.seekg(header.startPosition);
//We're currently not loading env or particle data
if (header.id == ChunkTypeID::FileInfo) {
ReadFileInfoChunk(file, header);
} else if (header.id == ChunkTypeID::SceneObjectData) {
ReadSceneObjectDataChunk(file, header);
}
m_ChunkHeaders.insert(std::make_pair(header.id, header));
file.seekg(target);
} else {
if (initPos == std::streamoff(0)) { //Really old chunk version
file.seekg(0);
Header header;
header.id = ChunkTypeID::FileInfo;
BinaryIO::BinaryRead(file, header.chunkVersion);
BinaryIO::BinaryRead(file, header.chunkType);
uint8_t important = 0;
BinaryIO::BinaryRead(file, important);
// file.ignore(1); //probably used
if (header.chunkVersion > 36) {
BinaryIO::BinaryRead(file, header.fileInfo.revision);
}
// HARDCODED 3
if (header.chunkVersion >= 45) file.ignore(4);
file.ignore(4 * (4 * 3));
if (header.chunkVersion >= 31) {
if (header.chunkVersion >= 39) {
file.ignore(12 * 4);
if (header.chunkVersion >= 40) {
uint32_t s = 0;
BinaryIO::BinaryRead(file, s);
for (uint32_t i = 0; i < s; ++i) {
file.ignore(4 * 3); //a uint and two floats
}
}
} else {
file.ignore(8);
}
file.ignore(3 * 4);
}
if (header.chunkVersion >= 36) {
file.ignore(3 * 4);
}
if (header.chunkVersion < 42) {
file.ignore(3 * 4);
if (header.chunkVersion >= 33) {
file.ignore(4 * 4);
}
}
// skydome info
uint32_t count = 0;
BinaryIO::BinaryRead(file, count);
file.ignore(count);
if (header.chunkVersion >= 33) {
for (uint32_t i = 0; i < 5; ++i) {
uint32_t count = 0;
BinaryIO::BinaryRead(file, count);
file.ignore(count);
}
}
// editor settings
if (!important && header.chunkVersion >= 37){
file.ignore(4);
uint32_t count = 0;
BinaryIO::BinaryRead(file, count);
file.ignore(count * 12);
}
header.id = ChunkTypeID::SceneObjectData;
header.fileInfo.version = header.chunkVersion;
ReadSceneObjectDataChunk(file, header);
m_ChunkHeaders.insert(std::make_pair(header.id, header));
} break;
}
}
}
void Level::ReadFileInfoChunk(std::istream& file, Header& header) {
BinaryIO::BinaryRead(file, header.fileInfo.version);
BinaryIO::BinaryRead(file, header.fileInfo.revision);
BinaryIO::BinaryRead(file, header.fileInfo.enviromentChunkStart);
BinaryIO::BinaryRead(file, header.fileInfo.objectChunkStart);
BinaryIO::BinaryRead(file, header.fileInfo.particleChunkStart);
}
void Level::ReadSceneObjectDataChunk(std::istream& file, Header& header) {
uint32_t objectsCount = 0;
BinaryIO::BinaryRead(file, objectsCount);
void Level::LoadSceneObjects(const std::vector<SceneObject>& objects) {
CDFeatureGatingTable* featureGatingTable = CDClientManager::GetTable<CDFeatureGatingTable>();
CDFeatureGating gating;
@@ -229,28 +122,7 @@ void Level::ReadSceneObjectDataChunk(std::istream& file, Header& header) {
const auto zoneControlObject = Game::zoneManager->GetZoneControlObject();
DluAssert(zoneControlObject != nullptr);
for (uint32_t i = 0; i < objectsCount; ++i) {
std::u16string ldfString;
SceneObject obj;
BinaryIO::BinaryRead(file, obj.id);
BinaryIO::BinaryRead(file, obj.lot);
if (header.fileInfo.version >= 38) {
int32_t tmp = 1;
BinaryIO::BinaryRead(file, tmp);
if (tmp > -1 && tmp < 11) obj.nodeType = tmp;
}
if (header.fileInfo.version >= 32) {
BinaryIO::BinaryRead(file, obj.glomId);
}
BinaryIO::BinaryRead(file, obj.position);
BinaryIO::BinaryRead(file, obj.rotation);
BinaryIO::BinaryRead(file, obj.scale);
BinaryIO::ReadString<uint32_t>(file, ldfString);
BinaryIO::BinaryRead(file, obj.value3);
for (const auto& obj : objects) {
//This is a little bit of a bodge, but because the alpha client (HF) doesn't store the
//spawn position / rotation like the later versions do, we need to check the LOT for the spawn pos & set it.
if (obj.lot == LOT_MARKER_PLAYER_START) {
@@ -258,11 +130,6 @@ void Level::ReadSceneObjectDataChunk(std::istream& file, Header& header) {
Game::zoneManager->GetZoneMut()->SetSpawnRot(obj.rotation);
}
for (const auto& token : GeneralUtils::SplitString(GeneralUtils::UTF16ToWTF8(ldfString), '\n')) {
obj.settings.ParseInsert(token);
}
// We should never have more than 1 zone control object
bool skipLoadingObject = obj.lot == zoneControlObject->GetLOT();
for (const auto& data : obj.settings | std::views::values) {

View File

@@ -3,37 +3,14 @@
#include <map>
#include <iostream>
#include "Zone.h"
#include "LevelFile.h"
class Level {
public:
enum ChunkTypeID : uint16_t {
FileInfo = 1000,
SceneEnviroment = 2000,
SceneObjectData,
SceneParticleData
};
using ChunkTypeID = LevelFile::ChunkTypeID;
using FileInfoChunk = LevelFile::FileInfoChunk;
enum ChunkTypes {
Enviroment,
Objects,
Particles
};
struct FileInfoChunk {
uint32_t version;
uint32_t revision;
uint32_t enviromentChunkStart;
uint32_t objectChunkStart;
uint32_t particleChunkStart;
};
struct Header {
uint32_t id;
uint16_t chunkVersion;
ChunkTypeID chunkType;
uint32_t size;
uint32_t startPosition;
FileInfoChunk fileInfo;
struct Header : LevelFile::ChunkHeader {
LWOSCENEID lwoSceneID;
};
@@ -47,7 +24,5 @@ private:
Zone* m_ParentZone;
//private functions:
void ReadChunks(std::istream& file);
void ReadFileInfoChunk(std::istream& file, Header& header);
void ReadSceneObjectDataChunk(std::istream& file, Header& header);
void LoadSceneObjects(const std::vector<SceneObject>& objects);
};

View File

@@ -53,54 +53,47 @@ void Zone::LoadZoneIntoMemory() {
}
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);
// The file itself is read by ZoneFile (dCommon), which the dashboard uses too
ZoneFile zoneFile;
zoneFile.Read(file);
m_FileFormatVersion = zoneFile.fileFormatVersion;
m_WorldID = zoneFile.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);
AddRevision(LWOSCENEID_INVALID, zoneFile.mapRevision);
if (m_FileFormatVersion >= Zone::FileFormatVersion::Beta) {
BinaryIO::BinaryRead(file, m_Spawnpoint);
BinaryIO::BinaryRead(file, m_SpawnpointRotation);
m_Spawnpoint = zoneFile.spawnpoint;
m_SpawnpointRotation = zoneFile.spawnpointRotation;
m_SceneCount = zoneFile.scenes.size();
for (auto& scene : zoneFile.scenes) {
LoadScene(std::move(scene));
}
if (m_FileFormatVersion <= Zone::FileFormatVersion::LateAlpha) {
uint8_t sceneCount;
BinaryIO::BinaryRead(file, sceneCount);
m_SceneCount = sceneCount;
} else BinaryIO::BinaryRead(file, m_SceneCount);
m_ZoneRawPath = zoneFile.zoneRawPath;
m_ZoneName = zoneFile.zoneName;
m_ZoneDesc = zoneFile.zoneDesc;
for (uint32_t i = 0; i < m_SceneCount; ++i) {
LoadScene(file);
}
m_SceneTransitions = std::move(zoneFile.sceneTransitions);
m_NumberOfSceneTransitionsLoaded = m_SceneTransitions.size();
//Read generic zone info:
BinaryIO::ReadString<uint8_t>(file, m_ZonePath, BinaryIO::ReadType::String);
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);
m_PathDataLength = zoneFile.pathDataLength;
m_PathChunkVersion = zoneFile.pathChunkVersion;
m_Paths = std::move(zoneFile.paths);
if (m_FileFormatVersion >= Zone::FileFormatVersion::PreAlpha) {
BinaryIO::BinaryRead(file, m_NumberOfSceneTransitionsLoaded);
for (uint32_t i = 0; i < m_NumberOfSceneTransitionsLoaded; ++i) {
LoadSceneTransition(file);
// We verify the waypoint heights against the navmesh because in many movement paths,
// the waypoint is located near 0 height,
if (dpWorld::IsLoaded()) {
for (auto& path : m_Paths) {
if (path.pathType != PathType::Movement) continue;
for (auto& waypoint : path.pathWaypoints) {
// 2000 should be large enough for every world.
waypoint.position.y = dpWorld::GetNavMesh()->GetHeightAtPoint(waypoint.position, 2000.0f);
}
}
}
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{};
@@ -220,38 +213,16 @@ void Zone::AddRevision(LWOSCENEID sceneID, uint32_t revision) {
}
}
void Zone::LoadScene(std::istream& file) {
void Zone::LoadScene(ZoneScene&& zoneScene) {
SceneRef scene;
static_cast<ZoneScene&>(scene) = std::move(zoneScene);
scene.level = nullptr;
LWOSCENEID lwoSceneID(LWOZONEID_INVALID, 0);
BinaryIO::ReadString<uint8_t>(file, scene.filename, BinaryIO::ReadType::String);
// Older files have no scene ID or layer; those stay 0
LWOSCENEID lwoSceneID(scene.id, scene.sceneType);
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(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) {
BinaryIO::BinaryRead(file, scene.color_r);
BinaryIO::BinaryRead(file, scene.color_b);
BinaryIO::BinaryRead(file, scene.color_g);
}
m_Scenes[lwoSceneID] = std::move(scene);
}
@@ -329,168 +300,3 @@ const Path* Zone::GetPath(std::string name) const {
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;
BinaryIO::BinaryRead(file, info.sceneID);
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);
}

View File

@@ -2,6 +2,7 @@
#include "dZMCommon.h"
#include "LDFFormat.h"
#include "ZoneFile.h"
#include "tinyxml2.h"
#include <string>
#include <vector>
@@ -13,194 +14,15 @@ namespace LUTriggers {
class Level;
enum class eWaypointCommandType : uint32_t;
struct WaypointCommand {
eWaypointCommandType command{};
std::string data;
};
struct SceneRef {
std::string filename;
uint32_t id{};
uint32_t sceneType{}; //0 = general, 1 = audio?
std::string name;
NiPoint3 unknown1;
float unknown2{};
uint8_t color_r{};
uint8_t color_g{};
uint8_t color_b{};
// A scene of the zone, with its level once loaded
struct SceneRef : ZoneScene {
std::unique_ptr<Level> level;
std::map<uint32_t, LUTriggers::Trigger*> triggers;
};
struct SceneTransitionInfo {
uint64_t sceneID{}; //id of the scene being transitioned to.
NiPoint3 position;
};
struct SceneTransition {
std::string name;
std::vector<SceneTransitionInfo> points;
float width{};
};
struct MovingPlatformPathWaypoint {
uint8_t lockPlayer{};
float wait{};
std::string departSound;
std::string arriveSound;
};
struct CameraPathWaypoint {
float time{};
float fov{};
float tension{};
float continuity{};
float bias{};
};
struct RacingPathWaypoint {
uint8_t isResetNode{};
uint8_t isNonHorizontalCamera{};
float planeWidth{};
float planeHeight{};
float shortestDistanceToEnd{};
};
struct PathWaypoint {
NiPoint3 position;
NiQuaternion rotation = QuatUtils::IDENTITY; // not included in all, but it's more convenient here
MovingPlatformPathWaypoint movingPlatform;
CameraPathWaypoint camera;
RacingPathWaypoint racing;
float speed{};
LwoNameValue config;
std::vector<WaypointCommand> commands;
};
enum class PathType : uint32_t {
Movement = 0,
MovingPlatform = 1,
Property = 2,
Camera = 3,
Spawner = 4,
Showcase = 5,
Race = 6,
Rail = 7
};
enum class PathBehavior : uint32_t {
Loop = 0,
Bounce = 1,
Once = 2
};
enum class PropertyPathType : int32_t {
Path = 0,
EntireZone = 1,
GenetatedRectangle = 2
};
enum class PropertyType : int32_t {
Premiere = 0,
Prize = 1,
LUP = 2,
Headspace = 3
};
enum class PropertyRentalPeriod : uint32_t {
Forever = 0,
Seconds = 1,
Minutes = 2,
Hours = 3,
Days = 4,
Weeks = 5,
Months = 6,
Years = 7
};
enum class PropertyAchievmentRequired : uint32_t {
None = 0,
Builder = 1,
Craftsman = 2,
SeniorBuilder = 3,
JourneyMan = 4,
MasterBuilder = 5,
Architect = 6,
SeniorArchitect = 7,
MasterArchitect = 8,
Visionary = 9,
Exemplar = 10
};
struct MovingPlatformPath {
std::string platformTravelSound;
uint8_t timeBasedMovement{};
};
struct PropertyPath {
PropertyPathType pathType{};
int32_t price{};
uint32_t rentalTime{};
uint64_t associatedZone{};
std::string displayName;
std::string displayDesc;
PropertyType type{};
uint32_t cloneLimit{};
float repMultiplier{};
PropertyRentalPeriod rentalPeriod{};
PropertyAchievmentRequired achievementRequired{};
// Player respawn coordinates in the main zone (not the property zone)
NiPoint3 playerZoneCoords;
float maxBuildHeight{};
};
struct CameraPath {
std::string nextPath;
uint8_t rotatePlayer{};
};
struct SpawnerPath {
LOT spawnedLOT{};
uint32_t respawnTime{};
int32_t maxToSpawn{};
uint32_t amountMaintained{};
LWOOBJID spawnerObjID;
uint8_t spawnerNetActive{};
};
struct Path {
uint32_t pathVersion{};
PathType pathType;
std::string pathName;
uint32_t flags{};
PathBehavior pathBehavior;
uint32_t waypointCount{};
std::vector<PathWaypoint> pathWaypoints;
SpawnerPath spawner;
MovingPlatformPath movingPlatform;
PropertyPath property;
CameraPath camera;
};
class Zone {
public:
enum class FileFormatVersion : uint32_t { //Times are guessed.
PrePreAlpha = 30,
PreAlpha = 32,
LatePreAlpha = 33,
EarlyAlpha = 35,
Alpha = 36,
LateAlpha = 37,
Beta = 38,
Launch = 39,
Auramar = 40,
Latest = 41
};
using FileFormatVersion = ZoneFile::FileFormatVersion;
public:
Zone(const LWOZONEID zoneID);
@@ -216,6 +38,7 @@ public:
const uint32_t GetChecksum() const { return m_CheckSum; }
LUTriggers::Trigger* GetTrigger(uint32_t sceneID, uint32_t triggerID) const;
const Path* GetPath(std::string name) const;
const std::vector<Path>& GetPaths() const { return m_Paths; }
uint32_t GetWorldID() const { return m_WorldID; }
[[nodiscard]] std::string GetZoneName() const { return m_ZoneName; }
@@ -254,9 +77,6 @@ private:
std::map<LWOSCENEID, uint32_t> m_MapRevisions; //rhs is the revision!
//private ("helper") functions:
void LoadScene(std::istream& file);
void LoadScene(ZoneScene&& zoneScene);
void LoadLUTriggers(std::string triggerFile, SceneRef& scene);
void LoadSceneTransition(std::istream& file);
SceneTransitionInfo LoadSceneTransitionInfo(std::istream& file);
void LoadPath(std::istream& file);
};

View File

@@ -3,19 +3,9 @@
#include "NiPoint3.h"
#include "NiQuaternion.h"
#include "LDFFormat.h"
#include "LevelFile.h" // SceneObject
#include <vector>
struct SceneObject {
LWOOBJID id;
LOT lot;
uint32_t nodeType;
uint32_t glomId;
NiPoint3 position;
NiQuaternion rotation = QuatUtils::IDENTITY;
float scale = 1.0f;
uint32_t value3;
LwoNameValue settings;
};
#define LOT_MARKER_PLAYER_START 1931
#define LOT_MARKET_CAMERA_TARGET 2182

View File

@@ -42,6 +42,8 @@ public:
void RemoveSpawner(LWOOBJID id);
std::vector<Spawner*> GetSpawnersByName(const std::string& spawnerName);
std::vector<Spawner*> GetSpawnersInGroup(const std::string& group);
// Every spawner in the zone (live events pick walkable places from their nodes)
const std::map<LWOOBJID, Spawner*>& GetSpawners() const { return m_Spawners; }
void Update(float deltaTime);
Entity* GetZoneControlObject() { return m_ZoneControlObject; }
bool GetPlayerLoseCoinOnDeath() { return m_PlayerLoseCoinsOnDeath; }