#include "Game.h" #include "Level.h" #include #include #include #include #include "BinaryIO.h" #include "Logger.h" #include "Spawner.h" #include "dZoneManager.h" #include "GeneralUtils.h" #include "Entity.h" #include "EntityManager.h" #include "CDFeatureGatingTable.h" #include "CDClientManager.h" #include "AssetManager.h" #include "dConfig.h" void Level::SceneObjectDataChunk::PrintAllObjects() const { for (const auto& [id, sceneObj] : objects) { LOG("ID: %d LOT: %d", id, sceneObj.lot); } } Level::Level(Zone* parentZone, const std::string& filepath) { m_ParentZone = parentZone; auto buffer = Game::assetManager->GetFileAsBuffer(filepath.c_str()); if (!buffer.m_Success) { LOG("Failed to load %s", filepath.c_str()); return; } std::istream file(&buffer); ReadChunks(file); buffer.close(); } Level::~Level() { for (auto& [id, header] : m_ChunkHeaders) { if (header.id == Level::ChunkTypeID::FileInfo) delete header.fileInfo; if (header.id == Level::ChunkTypeID::SceneObjectData) delete header.sceneObjects; } } void Level::MakeSpawner(SceneObject obj){ 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) { if (data) { 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, ';'); spawnInfo.groups.erase(spawnInfo.groups.end() - 1); } if (data->GetKey() == u"no_auto_spawn") { spawnInfo.noAutoSpawn = static_cast*>(data)->GetValue(); } if (data->GetKey() == u"no_timed_spawn") { spawnInfo.noTimedSpawn = static_cast*>(data)->GetValue(); } if (data->GetKey() == u"spawnActivator") { spawnInfo.spawnActivator = static_cast*>(data)->GetValue(); } } } Game::zoneManager->MakeSpawner(spawnInfo); } const void Level::PrintAllObjects() { for (std::map::iterator it = m_ChunkHeaders.begin(); it != m_ChunkHeaders.end(); ++it) { if (it->second.id == Level::ChunkTypeID::SceneObjectData) { it->second.sceneObjects->PrintAllObjects(); } } } void Level::ReadChunks(std::istream& file) { 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); } else if (header.id == ChunkTypeID::SceneObjectData) { ReadSceneObjectDataChunk(file, header); } m_ChunkHeaders.insert(std::make_pair(header.id, header)); file.seekg(target); } else { if (initPos == std::streamoff(0)) { //Really old chunk version file.seekg(0); Header header; header.id = ChunkTypeID::FileInfo; //I guess? FileInfoChunk* fileInfo = new FileInfoChunk(); BinaryIO::BinaryRead(file, header.chunkVersion); BinaryIO::BinaryRead(file, header.chunkType); file.ignore(1); BinaryIO::BinaryRead(file, fileInfo->revision); 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); //a uint file.ignore(4); //two floats file.ignore(4); } } } 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); } } 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); header.fileInfo = fileInfo; 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; } } } void Level::ReadFileInfoChunk(std::istream& file, Header& header) { FileInfoChunk* fi = new FileInfoChunk; BinaryIO::BinaryRead(file, fi->version); BinaryIO::BinaryRead(file, fi->revision); BinaryIO::BinaryRead(file, fi->enviromentChunkStart); BinaryIO::BinaryRead(file, fi->objectChunkStart); BinaryIO::BinaryRead(file, fi->particleChunkStart); header.fileInfo = fi; //PATCH FOR AG: (messed up file?) if (header.fileInfo->revision == 3452816845 && m_ParentZone->GetZoneID().GetMapID() == 1100) header.fileInfo->revision = 26; } void Level::ReadSceneObjectDataChunk(std::istream& file, Header& header) { SceneObjectDataChunk* chunk = new SceneObjectDataChunk; uint32_t objectsCount = 0; BinaryIO::BinaryRead(file, objectsCount); CDFeatureGatingTable* featureGatingTable = CDClientManager::Instance().GetTable(); CDFeatureGating gating; gating.major = 1; gating.current = 10; gating.minor = 64; GeneralUtils::TryParse(Game::config->GetValue("version_major"), gating.major); GeneralUtils::TryParse(Game::config->GetValue("version_current"), gating.current); GeneralUtils::TryParse(Game::config->GetValue("version_minor"), gating.minor); for (uint32_t i = 0; i < objectsCount; ++i) { SceneObject obj; BinaryIO::BinaryRead(file, obj.id); BinaryIO::BinaryRead(file, obj.lot); /*if (header.fileInfo->version >= 0x26)*/ BinaryIO::BinaryRead(file, obj.nodeType); /*if (header.fileInfo->version >= 0x20)*/ BinaryIO::BinaryRead(file, obj.glomId); BinaryIO::BinaryRead(file, obj.position); BinaryIO::BinaryRead(file, obj.rotation); BinaryIO::BinaryRead(file, obj.scale); //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. if (obj.lot == LOT_MARKER_PLAYER_START) { Game::zoneManager->GetZone()->SetSpawnPos(obj.position); Game::zoneManager->GetZone()->SetSpawnRot(obj.rotation); } std::u16string ldfString = u""; uint32_t length = 0; BinaryIO::BinaryRead(file, length); for (uint32_t i = 0; i < length; ++i) { uint16_t data; BinaryIO::BinaryRead(file, data); ldfString.push_back(data); } std::string sData = GeneralUtils::UTF16ToWTF8(ldfString); std::stringstream ssData(sData); std::string token; char deliminator = '\n'; while (std::getline(ssData, token, deliminator)) { LDFBaseData* ldfData = LDFBaseData::DataFromString(token); obj.settings.push_back(ldfData); } BinaryIO::BinaryRead(file, obj.value3); // Feature gating bool gated = false; for (LDFBaseData* data : obj.settings) { if (data->GetKey() == u"gatingOnFeature") { gating.featureName = data->GetValueAsString(); if (gating.featureName == Game::config->GetValue("event_1")) break; else if (gating.featureName == Game::config->GetValue("event_2")) break; else if (gating.featureName == Game::config->GetValue("event_3")) break; else if (gating.featureName == Game::config->GetValue("event_4")) break; else if (gating.featureName == Game::config->GetValue("event_5")) break; else if (gating.featureName == Game::config->GetValue("event_6")) break; else if (gating.featureName == Game::config->GetValue("event_7")) break; else if (gating.featureName == Game::config->GetValue("event_8")) break; else if (!featureGatingTable->FeatureUnlocked(gating)) { gated = true; break; } } } if (gated) { for (auto* setting : obj.settings) { delete setting; } obj.settings.clear(); continue; } if (obj.lot == 176) { //Spawner MakeSpawner(obj); } else { //Regular object 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; //Check to see if we shouldn't be loading this: bool clientOnly = false; bool serverOnly = false; std::string featureGate = ""; for (LDFBaseData* data : obj.settings) { if (data) { if (data->GetKey() == u"loadOnClientOnly") { clientOnly = (bool)std::stoi(data->GetValueAsString()); break; } if (data->GetKey() == u"loadSrvrOnly") { serverOnly = (bool)std::stoi(data->GetValueAsString()); break; } } } if (!clientOnly) { // We should never have more than 1 zone control object const auto zoneControlObject = Game::zoneManager->GetZoneControlObject(); if (zoneControlObject != nullptr && info.lot == zoneControlObject->GetLOT()) goto deleteSettings; Game::entityManager->CreateEntity(info, nullptr); } else { deleteSettings: for (auto* setting : info.settings) { delete setting; setting = nullptr; } info.settings.clear(); obj.settings.clear(); } } } header.sceneObjects = chunk; }