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>
285 lines
6.4 KiB
C++
285 lines
6.4 KiB
C++
#pragma once
|
|
|
|
#include "dZMCommon.h"
|
|
#include "LDFFormat.h"
|
|
#include "NiColor.h"
|
|
#include "tinyxml2.h"
|
|
#include <string>
|
|
#include <vector>
|
|
#include <map>
|
|
#include "Raw.h"
|
|
|
|
namespace LUTriggers {
|
|
struct Trigger;
|
|
};
|
|
|
|
class Level;
|
|
|
|
enum class eWaypointCommandType : uint32_t;
|
|
|
|
struct WaypointCommand {
|
|
eWaypointCommandType command{};
|
|
std::string data;
|
|
};
|
|
|
|
|
|
enum class eSceneType : uint32_t {
|
|
General = 0,
|
|
Audio = 1,
|
|
};
|
|
|
|
struct SceneRef {
|
|
std::string filename;
|
|
uint32_t id{};
|
|
eSceneType sceneType{};
|
|
std::string name;
|
|
NiPoint3 unknown1;
|
|
float unknown2{};
|
|
NiColor color;
|
|
std::unique_ptr<Level> level;
|
|
std::map<uint32_t, LUTriggers::Trigger*> triggers;
|
|
};
|
|
|
|
struct SceneTransitionInfo {
|
|
LWOSCENEID sceneID;
|
|
NiPoint3 position;
|
|
};
|
|
|
|
// A zone boundary line from the .luz
|
|
struct ZoneBoundary {
|
|
NiPoint3 normal;
|
|
NiPoint3 point;
|
|
LWOZONEID destinationZone; // the clone ID is always 0
|
|
uint32_t destinationSceneID{};
|
|
NiPoint3 spawnLocation;
|
|
};
|
|
|
|
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
|
|
};
|
|
|
|
public:
|
|
Zone(const LWOZONEID zoneID);
|
|
~Zone();
|
|
|
|
void Initalize();
|
|
void LoadZoneIntoMemory();
|
|
std::string GetFilePathForZoneID() const;
|
|
uint32_t CalculateChecksum() const;
|
|
void LoadLevelsIntoMemory();
|
|
void AddRevision(LWOSCENEID sceneID, uint32_t revision);
|
|
const LWOZONEID& GetZoneID() const { return m_ZoneID; }
|
|
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;
|
|
|
|
uint32_t GetWorldID() const { return m_WorldID; }
|
|
[[nodiscard]] std::string GetZoneName() const { return m_ZoneName; }
|
|
std::string GetZoneRawPath() const { return m_ZoneRawPath; }
|
|
std::string GetZonePath() const { return m_ZonePath; }
|
|
|
|
const NiPoint3& GetSpawnPos() const { return m_Spawnpoint; }
|
|
const NiQuaternion& GetSpawnRot() const { return m_SpawnpointRotation; }
|
|
|
|
void SetSpawnPos(const NiPoint3& pos) { m_Spawnpoint = pos; }
|
|
void SetSpawnRot(const NiQuaternion& rot) { m_SpawnpointRotation = rot; }
|
|
|
|
const Raw::Raw& GetZoneRaw() const { return m_Raw; }
|
|
const SceneRef* GetScene(LWOSCENEID sceneID) const;
|
|
const std::vector<SceneTransition>& GetSceneTransitions() const { return m_SceneTransitions; }
|
|
const std::vector<ZoneBoundary>& GetBoundaries() const { return m_Boundaries; }
|
|
const std::map<LWOSCENEID, SceneRef>& GetScenes() const { return m_Scenes; }
|
|
|
|
private:
|
|
LWOZONEID m_ZoneID;
|
|
std::string m_ZoneFilePath;
|
|
uint32_t m_NumberOfObjectsLoaded;
|
|
uint32_t m_NumberOfSceneTransitionsLoaded;
|
|
FileFormatVersion m_FileFormatVersion;
|
|
uint32_t m_CheckSum;
|
|
uint32_t m_WorldID; //should be equal to the MapID
|
|
NiPoint3 m_Spawnpoint;
|
|
NiQuaternion m_SpawnpointRotation = QuatUtils::IDENTITY;
|
|
uint32_t m_SceneCount;
|
|
|
|
std::string m_ZonePath; //Path to the .luz's folder
|
|
std::string m_ZoneName; //Name given to the zone by a level designer
|
|
std::string m_ZoneDesc; //Description of the zone by a level designer
|
|
std::string m_ZoneRawPath; //Path to the .raw file of this zone.
|
|
Raw::Raw m_Raw; // The Raw data for this zone
|
|
|
|
std::map<LWOSCENEID, SceneRef> m_Scenes;
|
|
std::vector<SceneTransition> m_SceneTransitions;
|
|
std::vector<ZoneBoundary> m_Boundaries;
|
|
|
|
uint32_t m_PathDataLength;
|
|
uint32_t m_PathChunkVersion;
|
|
std::vector<Path> m_Paths;
|
|
|
|
std::map<LWOSCENEID, uint32_t> m_MapRevisions; //rhs is the revision!
|
|
//private ("helper") functions:
|
|
void LoadScene(std::istream& file);
|
|
void LoadLUTriggers(std::string triggerFile, SceneRef& scene);
|
|
void LoadSceneTransition(std::istream& file);
|
|
SceneTransitionInfo LoadSceneTransitionInfo(std::istream& file);
|
|
void LoadPath(std::istream& file);
|
|
};
|