Files
DarkflameServer/dCommon/ZoneFile.h
Aaron Kimbrell 9f390958f5 fix(zone): scenes of version 30-32 zone files are numbered like the client
Zone files before version 33 have no scene IDs. The client gives each
scene its index in the file as its ID, -1 past 255, with layer 0
(LuzReader::ReadScenes, 1.10.64, via the LWOSceneID from the loop
index); the reader left every such scene at ID 0, so a zone with several
scenes had them all collide on one ID.

The version 30 zones on disk have one scene each, so what they read is
unchanged.

Check in game: nothing to check on live worlds (all are version 36+).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 08:36:35 -05:00

266 lines
6.3 KiB
C++

#pragma once
#include <cstdint>
#include <functional>
#include <optional>
#include <iosfwd>
#include <map>
#include <string>
#include <vector>
#include "dCommonVars.h"
#include "NiPoint3.h"
#include "NiQuaternion.h"
#include "LDFFormat.h"
#include "eSceneType.h"
#include "eWaypointCommandType.h"
/**
* A zone file (.luz) as the world server loads it (Zone::LoadZoneIntoMemory): its scenes, scene transitions and paths.
* Only reads; what the world does with the result (spawners, triggers, navmesh heights) stays in dZoneManager, so the
* dashboard reads zones with the same code.
*/
struct WaypointCommand {
eWaypointCommandType command{};
std::string data;
};
struct ZoneScene {
std::string filename;
uint32_t id{};
uint32_t sceneTableID{}; // before PrePreAlpha: the scene's row in the CDClient SceneTable (see ResolveSceneTable)
eSceneType sceneType{};
std::string name;
NiPoint3 unknown1;
float unknown2{};
uint8_t color_r{};
uint8_t color_g{};
uint8_t color_b{};
};
// A line the player crosses to leave the zone (the client's LuzReader::ReadZoneBoundaryLines)
struct ZoneBoundary {
NiPoint3 normal;
NiPoint3 point;
LWOZONEID destZoneID; // clone 0
uint32_t destSceneID{};
NiPoint3 spawnLocation;
};
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 = 1; // not in paths before version 9; the client then activates it on load
};
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;
};
struct ZoneFile {
enum class FileFormatVersion : uint32_t { //Times are guessed.
Oldest = 20, // the client reads anything older as this
PrePreAlpha = 30,
PreAlpha = 32,
LatePreAlpha = 33,
EarlyAlpha = 35,
Alpha = 36,
LateAlpha = 37,
Beta = 38,
Launch = 39,
Auramar = 40,
Latest = 41
};
FileFormatVersion fileFormatVersion{};
uint32_t mapRevision{};
uint32_t worldID{}; //should be equal to the MapID
NiPoint3 spawnpoint;
NiQuaternion spawnpointRotation = QuatUtils::IDENTITY;
std::vector<ZoneScene> scenes;
std::vector<ZoneBoundary> zoneBoundaries;
std::string zoneRawPath; //Path to the .raw file of this zone.
std::string zoneName; //Name given to the zone by a level designer
std::string zoneDesc; //Description of the zone by a level designer
std::vector<SceneTransition> sceneTransitions;
uint32_t pathDataLength{};
uint32_t pathChunkVersion{};
std::vector<Path> paths;
/**
* Reads the whole file. Throws std::runtime_error (BinaryIO) if it ends early; the last value read may still come up
* short without a throw, so callers that must know check the stream afterwards.
*/
void Read(std::istream& file);
/**
* Reads only the start of the file: the version, spawn point, scenes and the terrain file's name, leaving the scene
* transitions and paths (most of the file in big zones) unread. Throws as Read does.
*/
void ReadHeader(std::istream& file);
/**
* Before PrePreAlpha a scene is only its SceneTable ID. As the client does (ZoneLoader::ReadZoneFile), the scenes
* are taken in SceneTable ID order, each one `sceneName` finds a row for gets that row's sceneName as its file and
* name and the next index as its ID, and the others are left out. Nothing to do for newer files.
*/
void ResolveSceneTable(const std::function<std::optional<std::string>(uint32_t)>& sceneName);
private:
void ReadScene(std::istream& file, uint32_t index);
void ReadZoneBoundaries(std::istream& file);
void ReadSceneTransition(std::istream& file);
SceneTransitionInfo ReadSceneTransitionInfo(std::istream& file);
void ReadPath(std::istream& file);
// A waypoint's name/value pairs: waypoint commands on movement and rail paths, LDF config on the others
static void ReadLdfConfig(std::istream& file, PathType pathType, PathWaypoint& waypoint);
};