feat: add eSceneType enum, filter scene graph to general scenes, zone parsing improvements

- Add eSceneType enum (General, Audio) replacing raw uint32_t in SceneRef
- Filter BuildSceneGraph to only include General scenes
- Skip transitions referencing non-general scenes in adjacency graph
- Rename SceneRef unknown fields to scenePosition/sceneRadius
- Zone parsing and Level improvements

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-06-21 11:38:47 -05:00
parent 1aeede3cd1
commit 7dd918d894
7 changed files with 386 additions and 112 deletions

View File

@@ -107,7 +107,7 @@ void Level::ReadChunks(std::istream& file) {
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
if (header == CHNK_HEADER) {
Header header;
BinaryIO::BinaryRead(file, header.id);
BinaryIO::BinaryRead(file, header.chunkVersion);
@@ -118,83 +118,39 @@ void Level::ReadChunks(std::istream& file) {
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::SceneEnviroment) {
ReadEnvironmentChunk(file, header);
} else if (header.id == ChunkTypeID::SceneObjectData) {
ReadSceneObjectDataChunk(file, header);
} else if (header.id == ChunkTypeID::SceneParticleData) {
ReadParticleChunk(file, header);
}
m_ChunkHeaders.insert(std::make_pair(header.id, header));
file.seekg(target);
} else {
if (initPos == std::streamoff(0)) { //Really old chunk version
if (initPos == std::streamoff(0)) {
// Old LVL format without CHNK headers — environment + objects inline
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
uint8_t hasEditorData = 0;
BinaryIO::BinaryRead(file, hasEditorData);
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);
// Read environment data inline (no absolute offsets in old format)
ReadLighting(file, header.chunkVersion);
ReadSkydome(file, header.chunkVersion);
if (!hasEditorData && header.chunkVersion >= 37) {
ReadEditor(file);
}
m_HasEnvironment = true;
header.id = ChunkTypeID::SceneObjectData;
header.fileInfo.version = header.chunkVersion;
@@ -213,6 +169,157 @@ void Level::ReadFileInfoChunk(std::istream& file, Header& header) {
BinaryIO::BinaryRead(file, header.fileInfo.particleChunkStart);
}
void Level::ReadLighting(std::istream& file, uint32_t version) {
auto& li = m_Environment.lighting;
if (version >= 45) BinaryIO::BinaryRead(file, li.blendTime);
for (float& f : li.ambient) BinaryIO::BinaryRead(file, f);
for (float& f : li.specular) BinaryIO::BinaryRead(file, f);
for (float& f : li.upperHemi) BinaryIO::BinaryRead(file, f);
BinaryIO::BinaryRead(file, li.position);
if (version >= 39) {
li.hasDrawDistances = true;
auto readDD = [&](LvlDrawDistances& dd) {
BinaryIO::BinaryRead(file, dd.fogNear);
BinaryIO::BinaryRead(file, dd.fogFar);
BinaryIO::BinaryRead(file, dd.postFogSolid);
BinaryIO::BinaryRead(file, dd.postFogFade);
BinaryIO::BinaryRead(file, dd.staticObjDistance);
BinaryIO::BinaryRead(file, dd.dynamicObjDistance);
};
readDD(li.minDraw);
readDD(li.maxDraw);
}
if (version >= 40) {
uint32_t numCull = 0;
BinaryIO::BinaryRead(file, numCull);
li.cullVals.reserve(numCull);
for (uint32_t i = 0; i < numCull; ++i) {
LvlCullVal cv;
BinaryIO::BinaryRead(file, cv.groupID);
BinaryIO::BinaryRead(file, cv.min);
BinaryIO::BinaryRead(file, cv.max);
li.cullVals.push_back(cv);
}
}
if (version >= 31 && version < 39) {
BinaryIO::BinaryRead(file, li.fogNear);
BinaryIO::BinaryRead(file, li.fogFar);
}
if (version >= 31) {
for (float& f : li.fogColor) BinaryIO::BinaryRead(file, f);
}
if (version >= 36) {
for (float& f : li.dirLight) BinaryIO::BinaryRead(file, f);
}
if (version < 42) {
li.hasSpawn = true;
BinaryIO::BinaryRead(file, li.startPosition);
if (version >= 33) {
BinaryIO::BinaryRead(file, li.startRotation.w);
BinaryIO::BinaryRead(file, li.startRotation.x);
BinaryIO::BinaryRead(file, li.startRotation.y);
BinaryIO::BinaryRead(file, li.startRotation.z);
}
}
}
void Level::ReadSkydome(std::istream& file, uint32_t version) {
auto& si = m_Environment.skydome;
BinaryIO::ReadString<uint32_t>(file, si.filename, BinaryIO::ReadType::String);
if (version >= 34) {
BinaryIO::ReadString<uint32_t>(file, si.skyLayerFilename, BinaryIO::ReadType::String);
for (auto& rl : si.ringLayer) {
BinaryIO::ReadString<uint32_t>(file, rl, BinaryIO::ReadType::String);
}
}
}
void Level::ReadEditor(std::istream& file) {
auto& ed = m_Environment.editor;
uint32_t blockSize = 0;
BinaryIO::BinaryRead(file, blockSize);
uint32_t numColors = 0;
BinaryIO::BinaryRead(file, numColors);
ed.savedColors.reserve(numColors);
for (uint32_t i = 0; i < numColors; ++i) {
LvlEditorColor c;
BinaryIO::BinaryRead(file, c.r);
BinaryIO::BinaryRead(file, c.g);
BinaryIO::BinaryRead(file, c.b);
ed.savedColors.push_back(c);
}
m_Environment.hasEditor = true;
}
void Level::ReadEnvironmentChunk(std::istream& file, Header& header) {
uint32_t version = 0;
// Find the version from the fib chunk if we've already read it
auto fibIt = m_ChunkHeaders.find(ChunkTypeID::FileInfo);
if (fibIt != m_ChunkHeaders.end()) {
version = fibIt->second.fileInfo.version;
}
// Environment chunk payload: 3 absolute u32 offsets
uint32_t ofsLighting, ofsSkydome, ofsEditor;
BinaryIO::BinaryRead(file, ofsLighting);
BinaryIO::BinaryRead(file, ofsSkydome);
BinaryIO::BinaryRead(file, ofsEditor);
if (ofsLighting > 0) {
file.seekg(ofsLighting);
ReadLighting(file, version);
}
if (ofsSkydome > 0) {
file.seekg(ofsSkydome);
ReadSkydome(file, version);
}
if (version >= 37 && ofsEditor > 0) {
file.seekg(ofsEditor);
ReadEditor(file);
}
m_HasEnvironment = true;
}
void Level::ReadParticleChunk(std::istream& file, Header& header) {
uint32_t version = 0;
auto fibIt = m_ChunkHeaders.find(ChunkTypeID::FileInfo);
if (fibIt != m_ChunkHeaders.end()) {
version = fibIt->second.fileInfo.version;
}
uint32_t count = 0;
BinaryIO::BinaryRead(file, count);
m_Particles.reserve(count);
for (uint32_t i = 0; i < count; ++i) {
LvlParticle p;
if (version >= 43) BinaryIO::BinaryRead(file, p.priority);
BinaryIO::BinaryRead(file, p.position);
BinaryIO::BinaryRead(file, p.rotation.w);
BinaryIO::BinaryRead(file, p.rotation.x);
BinaryIO::BinaryRead(file, p.rotation.y);
BinaryIO::BinaryRead(file, p.rotation.z);
// effect_names: u4_wstr
BinaryIO::ReadString<uint32_t>(file, p.effectNames, BinaryIO::ReadType::WideString);
// null terminator (version < 46)
if (version < 46) {
uint16_t null_term;
BinaryIO::BinaryRead(file, null_term);
}
// config: u4_wstr parsed as LDF
std::string configStr;
BinaryIO::ReadString<uint32_t>(file, configStr, BinaryIO::ReadType::WideString);
for (const auto& token : GeneralUtils::SplitString(configStr, '\n')) {
p.config.ParseInsert(token);
}
m_Particles.push_back(std::move(p));
}
}
void Level::ReadSceneObjectDataChunk(std::istream& file, Header& header) {
uint32_t objectsCount = 0;
BinaryIO::BinaryRead(file, objectsCount);
@@ -236,9 +343,9 @@ void Level::ReadSceneObjectDataChunk(std::istream& file, Header& header) {
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;
uint32_t nodeType;
BinaryIO::BinaryRead(file, nodeType);
obj.nodeType = (nodeType <= 15) ? nodeType : 1;
}
if (header.fileInfo.version >= 32) {
@@ -249,7 +356,29 @@ void Level::ReadSceneObjectDataChunk(std::istream& file, Header& header) {
BinaryIO::BinaryRead(file, obj.rotation);
BinaryIO::BinaryRead(file, obj.scale);
BinaryIO::ReadString<uint32_t>(file, ldfString);
BinaryIO::BinaryRead(file, obj.value3);
if (header.fileInfo.version >= 7) {
uint32_t numRenderAttrs = 0;
BinaryIO::BinaryRead(file, numRenderAttrs);
if (numRenderAttrs > 0) {
char nameBuf[64]{};
file.read(nameBuf, 64);
obj.renderTechnique.name.assign(nameBuf, strnlen(nameBuf, 64));
obj.renderTechnique.attrs.resize(numRenderAttrs);
for (uint32_t a = 0; a < numRenderAttrs; ++a) {
auto& attr = obj.renderTechnique.attrs[a];
char attrName[64]{};
file.read(attrName, 64);
attr.name.assign(attrName, strnlen(attrName, 64));
BinaryIO::BinaryRead(file, attr.numFloats);
uint8_t isColor = 0;
BinaryIO::BinaryRead(file, isColor);
attr.isColor = isColor != 0;
for (float& f : attr.values) BinaryIO::BinaryRead(file, f);
}
}
}
//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.