refactor(zone): LWOSCENEID lives with the zone code, its layer is an eSceneType

LWOSCENEID and LWOSCENEID_INVALID move from dCommonVars.h to
dZoneManager/LWOSCENEID.h; only the zone code uses them. The layer half
is the scene's layer, now the new eSceneType (dCommon/dEnums) instead of
a uint32_t: General (0), Audio (1, the *_audio.lvl scenes named
"Audio") and FX (2, Nexus Tower's *_fxs.lvl scenes named "FXs"), the
three layers the zone files of every client on disk use. ZoneScene's
sceneType is an eSceneType too, so a scene's LWOSCENEID is built from it
directly, and the dashboard compares against eSceneType::General instead
of 0. The zone checksum still hashes the layer's number.

Check in game: every world loads (the zone checksum the client checks is
unchanged), Nexus Tower included.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-09-29 04:49:42 -05:00
parent 0143ed834c
commit d5563ad5da
10 changed files with 58 additions and 33 deletions

View File

@@ -10,6 +10,7 @@
#include "NiPoint3.h" #include "NiPoint3.h"
#include "NiQuaternion.h" #include "NiQuaternion.h"
#include "LDFFormat.h" #include "LDFFormat.h"
#include "eSceneType.h"
#include "eWaypointCommandType.h" #include "eWaypointCommandType.h"
/** /**
@@ -26,7 +27,7 @@ struct WaypointCommand {
struct ZoneScene { struct ZoneScene {
std::string filename; std::string filename;
uint32_t id{}; uint32_t id{};
uint32_t sceneType{}; //0 = general, 1 = audio? eSceneType sceneType{};
std::string name; std::string name;
NiPoint3 unknown1; NiPoint3 unknown1;
float unknown2{}; float unknown2{};

View File

@@ -54,32 +54,6 @@ constexpr float PI = 3.14159f;
//============ STRUCTS ============== //============ STRUCTS ==============
struct LWOSCENEID {
public:
constexpr LWOSCENEID() noexcept { m_sceneID = -1; m_layerID = 0; }
constexpr LWOSCENEID(int32_t sceneID) noexcept { m_sceneID = sceneID; m_layerID = 0; }
constexpr LWOSCENEID(int32_t sceneID, uint32_t layerID) noexcept { m_sceneID = sceneID; m_layerID = layerID; }
constexpr LWOSCENEID& operator=(const LWOSCENEID& rhs) noexcept { m_sceneID = rhs.m_sceneID; m_layerID = rhs.m_layerID; return *this; }
constexpr LWOSCENEID& operator=(const int32_t rhs) noexcept { m_sceneID = rhs; m_layerID = 0; return *this; }
constexpr bool operator<(const LWOSCENEID& rhs) const noexcept { return (m_sceneID < rhs.m_sceneID || (m_sceneID == rhs.m_sceneID && m_layerID < rhs.m_layerID)); }
constexpr bool operator<(const int32_t rhs) const noexcept { return m_sceneID < rhs; }
constexpr bool operator==(const LWOSCENEID& rhs) const noexcept { return (m_sceneID == rhs.m_sceneID && m_layerID == rhs.m_layerID); }
constexpr bool operator==(const int32_t rhs) const noexcept { return m_sceneID == rhs; }
constexpr int32_t GetSceneID() const noexcept { return m_sceneID; }
constexpr uint32_t GetLayerID() const noexcept { return m_layerID; }
constexpr void SetSceneID(const int32_t sceneID) noexcept { m_sceneID = sceneID; }
constexpr void SetLayerID(const uint32_t layerID) noexcept { m_layerID = layerID; }
private:
int32_t m_sceneID;
uint32_t m_layerID;
};
struct LWOZONEID { struct LWOZONEID {
public: public:
constexpr const LWOMAPID& GetMapID() const noexcept { return m_MapID; } constexpr const LWOMAPID& GetMapID() const noexcept { return m_MapID; }
@@ -99,7 +73,6 @@ private:
LWOCLONEID m_CloneID = LWOCLONEID_INVALID; //To differentiate between "your property" and "my property". Always 0 for non-prop worlds. LWOCLONEID m_CloneID = LWOCLONEID_INVALID; //To differentiate between "your property" and "my property". Always 0 for non-prop worlds.
}; };
constexpr LWOSCENEID LWOSCENEID_INVALID = -1;
struct FriendData { struct FriendData {
public: public:

View File

@@ -0,0 +1,13 @@
#ifndef ESCENETYPE_H
#define ESCENETYPE_H
#include <cstdint>
// A zone scene's layer (the second half of the client's SceneAndLayer); zones split into a scene per layer
enum class eSceneType : uint32_t {
General = 0, // the scene's objects ("Global Scene" and the named scenes)
Audio = 1, // *_audio.lvl scenes, named "Audio"
FX = 2, // *_fxs.lvl scenes, named "FXs" (Nexus Tower)
};
#endif //!ESCENETYPE_H

View File

@@ -701,7 +701,7 @@ std::optional<std::string> ZoneTerrainLayersJson(uint32_t zoneId) {
const auto luz = ClientAssets::ReadResFile("maps/" + zone->luzPath); const auto luz = ClientAssets::ReadResFile("maps/" + zone->luzPath);
std::string error; std::string error;
if (const auto header = luz ? ZonePaths::ReadHeader(*luz, error) : std::nullopt) { if (const auto header = luz ? ZonePaths::ReadHeader(*luz, error) : std::nullopt) {
for (const auto& scene : header->scenes) if (scene.sceneType == 0) names.try_emplace(scene.id, scene.name); for (const auto& scene : header->scenes) if (scene.sceneType == eSceneType::General) names.try_emplace(scene.id, scene.name);
} }
std::map<uint8_t, uint64_t> cells; std::map<uint8_t, uint64_t> cells;
nlohmann::json chunks = nlohmann::json::array(); nlohmann::json chunks = nlohmann::json::array();

View File

@@ -373,7 +373,7 @@ namespace {
if (!lvl) continue; if (!lvl) continue;
const auto objectsBefore = assetOf.size(); const auto objectsBefore = assetOf.size();
const auto lighting = WorldScene::ReadLighting(*lvl); const auto lighting = WorldScene::ReadLighting(*lvl);
if (lighting && scene.sceneType == 0) lightingOf.try_emplace(scene.id, LightingJson(*lighting)); if (lighting && scene.sceneType == eSceneType::General) lightingOf.try_emplace(scene.id, LightingJson(*lighting));
if (sky < 0) { if (sky < 0) {
const auto skydome = JoinPath("", WorldScene::ReadSkydome(*lvl)); const auto skydome = JoinPath("", WorldScene::ReadSkydome(*lvl));
if (skydome.ends_with(".nif") && Files().paths.contains(skydome)) sky = static_cast<int64_t>(scenery.IndexOf(skydome)); if (skydome.ends_with(".nif") && Files().paths.contains(skydome)) sky = static_cast<int64_t>(scenery.IndexOf(skydome));

View File

@@ -138,7 +138,7 @@ namespace {
size_t index = 0; size_t index = 0;
for (const auto& scene : zoneFile->scenes) { for (const auto& scene : zoneFile->scenes) {
// Audio scenes share their general scene's id; the general one names it // Audio scenes share their general scene's id; the general one names it
if (scene.sceneType == 0 || !scenes.contains(std::to_string(scene.id))) scenes[std::to_string(scene.id)] = scene.name; if (scene.sceneType == eSceneType::General || !scenes.contains(std::to_string(scene.id))) scenes[std::to_string(scene.id)] = scene.name;
const auto lvl = ClientAssets::ReadResFile("maps/" + folder + scene.filename); const auto lvl = ClientAssets::ReadResFile("maps/" + folder + scene.filename);
if (!lvl) continue; if (!lvl) continue;
for (const auto& object : WorldScene::ReadObjects(*lvl)) { for (const auto& object : WorldScene::ReadObjects(*lvl)) {

37
dZoneManager/LWOSCENEID.h Normal file
View File

@@ -0,0 +1,37 @@
#ifndef LWOSCENEID_H
#define LWOSCENEID_H
#include <cstdint>
#include "eSceneType.h"
// A scene of a zone: its scene ID and layer
struct LWOSCENEID {
public:
constexpr LWOSCENEID() noexcept { m_sceneID = -1; m_layerID = eSceneType::General; }
constexpr LWOSCENEID(int32_t sceneID) noexcept { m_sceneID = sceneID; m_layerID = eSceneType::General; }
constexpr LWOSCENEID(int32_t sceneID, eSceneType layerID) noexcept { m_sceneID = sceneID; m_layerID = layerID; }
constexpr LWOSCENEID& operator=(const LWOSCENEID& rhs) noexcept { m_sceneID = rhs.m_sceneID; m_layerID = rhs.m_layerID; return *this; }
constexpr LWOSCENEID& operator=(const int32_t rhs) noexcept { m_sceneID = rhs; m_layerID = eSceneType::General; return *this; }
constexpr bool operator<(const LWOSCENEID& rhs) const noexcept { return (m_sceneID < rhs.m_sceneID || (m_sceneID == rhs.m_sceneID && m_layerID < rhs.m_layerID)); }
constexpr bool operator<(const int32_t rhs) const noexcept { return m_sceneID < rhs; }
constexpr bool operator==(const LWOSCENEID& rhs) const noexcept { return (m_sceneID == rhs.m_sceneID && m_layerID == rhs.m_layerID); }
constexpr bool operator==(const int32_t rhs) const noexcept { return m_sceneID == rhs; }
constexpr int32_t GetSceneID() const noexcept { return m_sceneID; }
constexpr eSceneType GetLayerID() const noexcept { return m_layerID; }
constexpr void SetSceneID(const int32_t sceneID) noexcept { m_sceneID = sceneID; }
constexpr void SetLayerID(const eSceneType layerID) noexcept { m_layerID = layerID; }
private:
int32_t m_sceneID;
eSceneType m_layerID;
};
constexpr LWOSCENEID LWOSCENEID_INVALID = -1;
#endif //!LWOSCENEID_H

View File

@@ -181,7 +181,7 @@ uint32_t Zone::CalculateChecksum() const {
sum2 += sum1 += (sceneID >> 16); sum2 += sum1 += (sceneID >> 16);
sum2 += sum1 += (sceneID & 0xffff); sum2 += sum1 += (sceneID & 0xffff);
uint32_t layerID = scene.GetLayerID(); uint32_t layerID = GeneralUtils::ToUnderlying(scene.GetLayerID());
sum2 += sum1 += (layerID >> 16); sum2 += sum1 += (layerID >> 16);
sum2 += sum1 += (layerID & 0xffff); sum2 += sum1 += (layerID & 0xffff);

View File

@@ -2,6 +2,7 @@
#include "dZMCommon.h" #include "dZMCommon.h"
#include "LDFFormat.h" #include "LDFFormat.h"
#include "LWOSCENEID.h"
#include "ZoneFile.h" #include "ZoneFile.h"
#include "ZoneScenes.h" #include "ZoneScenes.h"
#include "tinyxml2.h" #include "tinyxml2.h"

View File

@@ -65,7 +65,7 @@ TEST(ZoneFileTests, ReadsScenesAndPaths) {
ASSERT_EQ(zone.scenes.size(), 1u); ASSERT_EQ(zone.scenes.size(), 1u);
EXPECT_EQ(zone.scenes[0].filename, "scene.lvl"); EXPECT_EQ(zone.scenes[0].filename, "scene.lvl");
EXPECT_EQ(zone.scenes[0].id, 7u); EXPECT_EQ(zone.scenes[0].id, 7u);
EXPECT_EQ(zone.scenes[0].sceneType, 2u); EXPECT_EQ(zone.scenes[0].sceneType, eSceneType::FX);
EXPECT_EQ(zone.zoneRawPath, "zone.raw"); EXPECT_EQ(zone.zoneRawPath, "zone.raw");
ASSERT_EQ(zone.sceneTransitions.size(), 1u); ASSERT_EQ(zone.sceneTransitions.size(), 1u);
EXPECT_EQ(zone.sceneTransitions[0].points.size(), 2u); EXPECT_EQ(zone.sceneTransitions[0].points.size(), 2u);