2021-12-05 17:54:36 +00:00
|
|
|
#include "Game.h"
|
|
|
|
#include "Level.h"
|
|
|
|
#include <fstream>
|
|
|
|
#include <iostream>
|
|
|
|
#include <sstream>
|
|
|
|
#include <string>
|
|
|
|
#include "BinaryIO.h"
|
2023-10-21 23:31:55 +00:00
|
|
|
#include "Logger.h"
|
2021-12-05 17:54:36 +00:00
|
|
|
#include "Spawner.h"
|
|
|
|
#include "dZoneManager.h"
|
|
|
|
#include "GeneralUtils.h"
|
|
|
|
#include "Entity.h"
|
|
|
|
#include "EntityManager.h"
|
2021-12-20 10:25:45 +00:00
|
|
|
#include "CDFeatureGatingTable.h"
|
|
|
|
#include "CDClientManager.h"
|
2022-11-01 18:21:26 +00:00
|
|
|
#include "AssetManager.h"
|
2024-02-10 11:05:25 +00:00
|
|
|
#include "ClientVersion.h"
|
2023-11-18 09:33:23 +00:00
|
|
|
#include "dConfig.h"
|
2021-12-05 17:54:36 +00:00
|
|
|
|
|
|
|
Level::Level(Zone* parentZone, const std::string& filepath) {
|
2022-07-28 13:39:57 +00:00
|
|
|
m_ParentZone = parentZone;
|
2022-11-01 18:21:26 +00:00
|
|
|
|
2023-12-23 17:24:16 +00:00
|
|
|
auto stream = Game::assetManager->GetFile(filepath.c_str());
|
2022-11-01 18:21:26 +00:00
|
|
|
|
2023-12-23 17:24:16 +00:00
|
|
|
if (!stream) {
|
2023-10-21 23:31:55 +00:00
|
|
|
LOG("Failed to load %s", filepath.c_str());
|
2022-11-01 18:21:26 +00:00
|
|
|
return;
|
2021-12-05 17:54:36 +00:00
|
|
|
}
|
2023-12-23 17:24:16 +00:00
|
|
|
|
|
|
|
ReadChunks(stream);
|
2021-12-05 17:54:36 +00:00
|
|
|
}
|
|
|
|
|
2023-12-23 17:24:16 +00:00
|
|
|
void Level::MakeSpawner(SceneObject obj) {
|
2023-11-10 03:28:52 +00:00
|
|
|
SpawnerInfo spawnInfo = SpawnerInfo();
|
|
|
|
SpawnerNode* node = new SpawnerNode();
|
|
|
|
spawnInfo.templateID = obj.lot;
|
|
|
|
spawnInfo.spawnerID = obj.id;
|
|
|
|
spawnInfo.templateScale = obj.scale;
|
|
|
|
node->position = obj.position;
|
|
|
|
node->rotation = obj.rotation;
|
|
|
|
node->config = obj.settings;
|
|
|
|
spawnInfo.nodes.push_back(node);
|
|
|
|
for (LDFBaseData* data : obj.settings) {
|
2023-12-23 17:24:16 +00:00
|
|
|
if (!data) continue;
|
2023-11-10 03:28:52 +00:00
|
|
|
if (data->GetKey() == u"spawntemplate") {
|
|
|
|
spawnInfo.templateID = std::stoi(data->GetValueAsString());
|
|
|
|
}
|
|
|
|
|
|
|
|
if (data->GetKey() == u"spawner_node_id") {
|
|
|
|
node->nodeID = std::stoi(data->GetValueAsString());
|
|
|
|
}
|
|
|
|
|
|
|
|
if (data->GetKey() == u"spawner_name") {
|
|
|
|
spawnInfo.name = data->GetValueAsString();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (data->GetKey() == u"max_to_spawn") {
|
|
|
|
spawnInfo.maxToSpawn = std::stoi(data->GetValueAsString());
|
|
|
|
}
|
|
|
|
|
|
|
|
if (data->GetKey() == u"spawner_active_on_load") {
|
|
|
|
spawnInfo.activeOnLoad = std::stoi(data->GetValueAsString());
|
|
|
|
}
|
|
|
|
|
|
|
|
if (data->GetKey() == u"active_on_load") {
|
|
|
|
spawnInfo.activeOnLoad = std::stoi(data->GetValueAsString());
|
|
|
|
}
|
|
|
|
|
|
|
|
if (data->GetKey() == u"respawn") {
|
|
|
|
if (data->GetValueType() == eLDFType::LDF_TYPE_FLOAT) // Floats are in seconds
|
|
|
|
{
|
|
|
|
spawnInfo.respawnTime = std::stof(data->GetValueAsString());
|
|
|
|
} else if (data->GetValueType() == eLDFType::LDF_TYPE_U32) // Ints are in ms?
|
|
|
|
{
|
|
|
|
spawnInfo.respawnTime = std::stoul(data->GetValueAsString()) / 1000;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (data->GetKey() == u"spawnsGroupOnSmash") {
|
|
|
|
spawnInfo.spawnsOnSmash = std::stoi(data->GetValueAsString());
|
|
|
|
}
|
|
|
|
if (data->GetKey() == u"spawnNetNameForSpawnGroupOnSmash") {
|
|
|
|
spawnInfo.spawnOnSmashGroupName = data->GetValueAsString();
|
|
|
|
}
|
|
|
|
if (data->GetKey() == u"groupID") { // Load object groups
|
|
|
|
std::string groupStr = data->GetValueAsString();
|
|
|
|
spawnInfo.groups = GeneralUtils::SplitString(groupStr, ';');
|
2023-12-23 17:50:14 +00:00
|
|
|
if (spawnInfo.groups.back().empty()) spawnInfo.groups.erase(spawnInfo.groups.end() - 1);
|
2023-11-10 03:28:52 +00:00
|
|
|
}
|
|
|
|
if (data->GetKey() == u"no_auto_spawn") {
|
|
|
|
spawnInfo.noAutoSpawn = static_cast<LDFData<bool>*>(data)->GetValue();
|
|
|
|
}
|
|
|
|
if (data->GetKey() == u"no_timed_spawn") {
|
|
|
|
spawnInfo.noTimedSpawn = static_cast<LDFData<bool>*>(data)->GetValue();
|
|
|
|
}
|
|
|
|
if (data->GetKey() == u"spawnActivator") {
|
|
|
|
spawnInfo.spawnActivator = static_cast<LDFData<bool>*>(data)->GetValue();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-23 17:24:16 +00:00
|
|
|
Game::zoneManager->MakeSpawner(spawnInfo);
|
2021-12-05 17:54:36 +00:00
|
|
|
}
|
|
|
|
|
2022-11-01 18:21:26 +00:00
|
|
|
void Level::ReadChunks(std::istream& file) {
|
2021-12-05 17:54:36 +00:00
|
|
|
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);
|
2022-07-28 13:39:57 +00:00
|
|
|
} else if (header.id == ChunkTypeID::SceneObjectData) {
|
2021-12-05 17:54:36 +00:00
|
|
|
ReadSceneObjectDataChunk(file, header);
|
|
|
|
}
|
|
|
|
|
|
|
|
m_ChunkHeaders.insert(std::make_pair(header.id, header));
|
|
|
|
file.seekg(target);
|
2022-07-28 13:39:57 +00:00
|
|
|
} else {
|
2021-12-05 17:54:36 +00:00
|
|
|
if (initPos == std::streamoff(0)) { //Really old chunk version
|
|
|
|
file.seekg(0);
|
|
|
|
Header header;
|
|
|
|
header.id = ChunkTypeID::FileInfo; //I guess?
|
|
|
|
BinaryIO::BinaryRead(file, header.chunkVersion);
|
|
|
|
BinaryIO::BinaryRead(file, header.chunkType);
|
|
|
|
file.ignore(1);
|
2023-12-23 17:24:16 +00:00
|
|
|
BinaryIO::BinaryRead(file, header.fileInfo.revision);
|
2021-12-05 17:54:36 +00:00
|
|
|
|
|
|
|
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) {
|
2023-12-23 17:24:16 +00:00
|
|
|
file.ignore(4 * 3); //a uint and two floats
|
2021-12-05 17:54:36 +00:00
|
|
|
}
|
|
|
|
}
|
2022-07-28 13:39:57 +00:00
|
|
|
} else {
|
2021-12-05 17:54:36 +00:00
|
|
|
file.ignore(8);
|
|
|
|
}
|
|
|
|
|
|
|
|
file.ignore(3 * 4);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (header.chunkVersion >= 36) {
|
|
|
|
file.ignore(3 * 4);
|
|
|
|
}
|
2022-07-25 02:26:51 +00:00
|
|
|
|
2021-12-05 17:54:36 +00:00
|
|
|
if (header.chunkVersion < 42) {
|
|
|
|
file.ignore(3 * 4);
|
|
|
|
|
|
|
|
if (header.chunkVersion >= 33) {
|
|
|
|
file.ignore(4 * 4);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (uint32_t i = 0; i < 6; ++i) {
|
|
|
|
uint32_t count = 0;
|
|
|
|
BinaryIO::BinaryRead(file, count);
|
|
|
|
file.ignore(count);
|
|
|
|
}
|
|
|
|
|
|
|
|
file.ignore(4);
|
|
|
|
|
|
|
|
uint32_t count = 0;
|
|
|
|
BinaryIO::BinaryRead(file, count);
|
|
|
|
file.ignore(count * 12);
|
|
|
|
|
|
|
|
m_ChunkHeaders.insert(std::make_pair(header.id, header));
|
|
|
|
|
|
|
|
//Now pretend to be a normal file and read Objects chunk:
|
|
|
|
Header hdr;
|
|
|
|
hdr.id = ChunkTypeID::SceneObjectData;
|
|
|
|
ReadSceneObjectDataChunk(file, hdr);
|
|
|
|
m_ChunkHeaders.insert(std::make_pair(hdr.id, hdr));
|
|
|
|
} break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-01 18:21:26 +00:00
|
|
|
void Level::ReadFileInfoChunk(std::istream& file, Header& header) {
|
2023-12-23 17:24:16 +00:00
|
|
|
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);
|
2021-12-05 17:54:36 +00:00
|
|
|
|
|
|
|
//PATCH FOR AG: (messed up file?)
|
2023-12-23 17:24:16 +00:00
|
|
|
if (header.fileInfo.revision == 0xCDCDCDCD && m_ParentZone->GetZoneID().GetMapID() == 1100) header.fileInfo.revision = 26;
|
2021-12-05 17:54:36 +00:00
|
|
|
}
|
|
|
|
|
2022-11-01 18:21:26 +00:00
|
|
|
void Level::ReadSceneObjectDataChunk(std::istream& file, Header& header) {
|
2021-12-05 17:54:36 +00:00
|
|
|
uint32_t objectsCount = 0;
|
|
|
|
BinaryIO::BinaryRead(file, objectsCount);
|
|
|
|
|
2024-02-09 05:40:43 +00:00
|
|
|
CDFeatureGatingTable* featureGatingTable = CDClientManager::GetTable<CDFeatureGatingTable>();
|
2021-12-05 17:54:36 +00:00
|
|
|
|
2023-11-18 09:33:23 +00:00
|
|
|
CDFeatureGating gating;
|
2024-02-10 11:05:25 +00:00
|
|
|
gating.major =
|
|
|
|
GeneralUtils::TryParse<int32_t>(Game::config->GetValue("version_major")).value_or(ClientVersion::major);
|
|
|
|
gating.current =
|
|
|
|
GeneralUtils::TryParse<int32_t>(Game::config->GetValue("version_current")).value_or(ClientVersion::current);
|
|
|
|
gating.minor =
|
|
|
|
GeneralUtils::TryParse<int32_t>(Game::config->GetValue("version_minor")).value_or(ClientVersion::minor);
|
2023-11-18 09:33:23 +00:00
|
|
|
|
2023-12-23 17:24:16 +00:00
|
|
|
const auto zoneControlObject = Game::zoneManager->GetZoneControlObject();
|
|
|
|
DluAssert(zoneControlObject != nullptr);
|
2021-12-05 17:54:36 +00:00
|
|
|
for (uint32_t i = 0; i < objectsCount; ++i) {
|
2023-12-23 17:24:16 +00:00
|
|
|
std::u16string ldfString;
|
2021-12-05 17:54:36 +00:00
|
|
|
SceneObject obj;
|
|
|
|
BinaryIO::BinaryRead(file, obj.id);
|
|
|
|
BinaryIO::BinaryRead(file, obj.lot);
|
|
|
|
|
2023-11-22 02:04:23 +00:00
|
|
|
/*if (header.fileInfo->version >= 0x26)*/ BinaryIO::BinaryRead(file, obj.nodeType);
|
|
|
|
/*if (header.fileInfo->version >= 0x20)*/ BinaryIO::BinaryRead(file, obj.glomId);
|
2021-12-05 17:54:36 +00:00
|
|
|
|
|
|
|
BinaryIO::BinaryRead(file, obj.position);
|
|
|
|
BinaryIO::BinaryRead(file, obj.rotation);
|
|
|
|
BinaryIO::BinaryRead(file, obj.scale);
|
2023-12-23 17:24:16 +00:00
|
|
|
BinaryIO::ReadString<uint32_t>(file, ldfString);
|
|
|
|
BinaryIO::BinaryRead(file, obj.value3);
|
2021-12-05 17:54:36 +00:00
|
|
|
|
2022-07-25 02:26:51 +00:00
|
|
|
//This is a little bit of a bodge, but because the alpha client (HF) doesn't store the
|
2021-12-05 17:54:36 +00:00
|
|
|
//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) {
|
2023-07-17 22:55:33 +00:00
|
|
|
Game::zoneManager->GetZone()->SetSpawnPos(obj.position);
|
|
|
|
Game::zoneManager->GetZone()->SetSpawnRot(obj.rotation);
|
2021-12-05 17:54:36 +00:00
|
|
|
}
|
|
|
|
|
2022-07-28 13:39:57 +00:00
|
|
|
std::string sData = GeneralUtils::UTF16ToWTF8(ldfString);
|
|
|
|
std::stringstream ssData(sData);
|
|
|
|
std::string token;
|
|
|
|
char deliminator = '\n';
|
2022-07-25 02:26:51 +00:00
|
|
|
|
2022-07-28 13:39:57 +00:00
|
|
|
while (std::getline(ssData, token, deliminator)) {
|
|
|
|
LDFBaseData* ldfData = LDFBaseData::DataFromString(token);
|
|
|
|
obj.settings.push_back(ldfData);
|
|
|
|
}
|
2021-12-05 17:54:36 +00:00
|
|
|
|
|
|
|
|
2023-12-23 17:24:16 +00:00
|
|
|
// We should never have more than 1 zone control object
|
|
|
|
bool skipLoadingObject = obj.lot == zoneControlObject->GetLOT();
|
2021-12-05 17:54:36 +00:00
|
|
|
for (LDFBaseData* data : obj.settings) {
|
2023-12-23 17:24:16 +00:00
|
|
|
if (!data) continue;
|
2021-12-05 17:54:36 +00:00
|
|
|
if (data->GetKey() == u"gatingOnFeature") {
|
2023-11-18 09:33:23 +00:00
|
|
|
gating.featureName = data->GetValueAsString();
|
2023-12-23 17:24:16 +00:00
|
|
|
if (gating.featureName == Game::config->GetValue("event_1")) continue;
|
|
|
|
else if (gating.featureName == Game::config->GetValue("event_2")) continue;
|
|
|
|
else if (gating.featureName == Game::config->GetValue("event_3")) continue;
|
|
|
|
else if (gating.featureName == Game::config->GetValue("event_4")) continue;
|
|
|
|
else if (gating.featureName == Game::config->GetValue("event_5")) continue;
|
|
|
|
else if (gating.featureName == Game::config->GetValue("event_6")) continue;
|
|
|
|
else if (gating.featureName == Game::config->GetValue("event_7")) continue;
|
|
|
|
else if (gating.featureName == Game::config->GetValue("event_8")) continue;
|
2023-11-18 09:33:23 +00:00
|
|
|
else if (!featureGatingTable->FeatureUnlocked(gating)) {
|
2023-12-23 17:24:16 +00:00
|
|
|
// The feature is not unlocked, so we can skip loading this object
|
|
|
|
skipLoadingObject = true;
|
2021-12-05 17:54:36 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2023-12-23 17:24:16 +00:00
|
|
|
// If this is a client only object, we can skip loading it
|
|
|
|
if (data->GetKey() == u"loadOnClientOnly") {
|
2023-12-24 17:17:04 +00:00
|
|
|
skipLoadingObject |= static_cast<bool>(std::stoi(data->GetValueAsString()));
|
2023-12-23 17:24:16 +00:00
|
|
|
break;
|
|
|
|
}
|
2021-12-05 17:54:36 +00:00
|
|
|
}
|
|
|
|
|
2023-12-23 17:24:16 +00:00
|
|
|
if (skipLoadingObject) {
|
2021-12-05 17:54:36 +00:00
|
|
|
for (auto* setting : obj.settings) {
|
|
|
|
delete setting;
|
2023-12-23 17:24:16 +00:00
|
|
|
setting = nullptr;
|
2021-12-05 17:54:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2022-07-28 13:39:57 +00:00
|
|
|
if (obj.lot == 176) { //Spawner
|
2023-11-10 03:28:52 +00:00
|
|
|
MakeSpawner(obj);
|
2022-07-28 13:39:57 +00:00
|
|
|
} else { //Regular object
|
2021-12-05 17:54:36 +00:00
|
|
|
EntityInfo info;
|
|
|
|
info.spawnerID = 0;
|
|
|
|
info.id = obj.id;
|
|
|
|
info.lot = obj.lot;
|
|
|
|
info.pos = obj.position;
|
|
|
|
info.rot = obj.rotation;
|
|
|
|
info.settings = obj.settings;
|
|
|
|
info.scale = obj.scale;
|
2023-12-23 17:24:16 +00:00
|
|
|
Game::entityManager->CreateEntity(info);
|
2021-12-05 17:54:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|