#include "Zone.h" #include "Level.h" #include #include #include "Game.h" #include "Logger.h" #include "GeneralUtils.h" #include "BinaryIO.h" #include "LUTriggers.h" #include "AssetManager.h" #include "CDClientManager.h" #include "CDZoneTableTable.h" #include "Spawner.h" #include "dZoneManager.h" #include "dpWorld.h" #include "eTriggerCommandType.h" #include "eTriggerEventType.h" Zone::Zone(const LWOMAPID& mapID, const LWOINSTANCEID& instanceID, const LWOCLONEID& cloneID) : m_ZoneID(mapID, instanceID, cloneID) { m_NumberOfScenesLoaded = 0; m_NumberOfObjectsLoaded = 0; m_NumberOfSceneTransitionsLoaded = 0; m_CheckSum = 0; m_WorldID = 0; m_SceneCount = 0; } Zone::~Zone() { LOG("Destroying zone %i", m_ZoneID.GetMapID()); for (std::map::iterator it = m_Scenes.begin(); it != m_Scenes.end(); ++it) { if (it->second.level != nullptr) delete it->second.level; } } void Zone::Initalize() { LoadZoneIntoMemory(); LoadLevelsIntoMemory(); m_CheckSum = CalculateChecksum(); } void Zone::LoadZoneIntoMemory() { m_ZoneFilePath = GetFilePathForZoneID(); m_ZonePath = m_ZoneFilePath.substr(0, m_ZoneFilePath.rfind('/') + 1); if (m_ZoneFilePath == "ERR") return; AssetMemoryBuffer buffer = Game::assetManager->GetFileAsBuffer(m_ZoneFilePath.c_str()); if (!buffer.m_Success) { LOG("Failed to load %s", m_ZoneFilePath.c_str()); throw std::runtime_error("Aborting Zone loading due to no Zone File."); } std::istream file(&buffer); if (file) { BinaryIO::BinaryRead(file, m_FileFormatVersion); uint32_t mapRevision = 0; if (m_FileFormatVersion >= Zone::FileFormatVersion::Alpha) BinaryIO::BinaryRead(file, mapRevision); BinaryIO::BinaryRead(file, m_WorldID); if ((uint16_t)m_WorldID != m_ZoneID.GetMapID()) LOG("WorldID: %i doesn't match MapID %i! Is this intended?", m_WorldID, m_ZoneID.GetMapID()); AddRevision(LWOSCENEID_INVALID, mapRevision); if (m_FileFormatVersion >= Zone::FileFormatVersion::Beta) { BinaryIO::BinaryRead(file, m_Spawnpoint); BinaryIO::BinaryRead(file, m_SpawnpointRotation); } if (m_FileFormatVersion <= Zone::FileFormatVersion::LateAlpha) { uint8_t sceneCount; BinaryIO::BinaryRead(file, sceneCount); m_SceneCount = sceneCount; } else BinaryIO::BinaryRead(file, m_SceneCount); for (uint32_t i = 0; i < m_SceneCount; ++i) { LoadScene(file); } //Read generic zone info: uint8_t stringLength; BinaryIO::BinaryRead(file, stringLength); m_ZonePath = BinaryIO::ReadString(file, stringLength); BinaryIO::BinaryRead(file, stringLength); m_ZoneRawPath = BinaryIO::ReadString(file, stringLength); BinaryIO::BinaryRead(file, stringLength); m_ZoneName = BinaryIO::ReadString(file, stringLength); BinaryIO::BinaryRead(file, stringLength); m_ZoneDesc = BinaryIO::ReadString(file, stringLength); if (m_FileFormatVersion >= Zone::FileFormatVersion::PreAlpha) { BinaryIO::BinaryRead(file, m_NumberOfSceneTransitionsLoaded); for (uint32_t i = 0; i < m_NumberOfSceneTransitionsLoaded; ++i) { LoadSceneTransition(file); } } if (m_FileFormatVersion >= Zone::FileFormatVersion::EarlyAlpha) { BinaryIO::BinaryRead(file, m_PathDataLength); BinaryIO::BinaryRead(file, m_PathChunkVersion); // always should be 1 uint32_t pathCount; BinaryIO::BinaryRead(file, pathCount); for (uint32_t i = 0; i < pathCount; ++i) LoadPath(file); for (Path path : m_Paths) { if (path.pathType == PathType::Spawner) { SpawnerInfo info = SpawnerInfo(); for (PathWaypoint waypoint : path.pathWaypoints) { SpawnerNode* node = new SpawnerNode(); node->position = waypoint.position; node->rotation = waypoint.rotation; node->nodeID = 0; node->config = waypoint.config; for (LDFBaseData* data : waypoint.config) { if (data) { if (data->GetKey() == u"spawner_node_id") { node->nodeID = std::stoi(data->GetValueAsString()); } else if (data->GetKey() == u"spawner_max_per_node") { node->nodeMax = std::stoi(data->GetValueAsString()); } else if (data->GetKey() == u"groupID") { // Load object group std::string groupStr = data->GetValueAsString(); info.groups = GeneralUtils::SplitString(groupStr, ';'); info.groups.erase(info.groups.end() - 1); } else if (data->GetKey() == u"grpNameQBShowBricks") { if (data->GetValueAsString() == "") continue; /*std::string groupStr = data->GetValueAsString(); info.groups.push_back(groupStr);*/ info.grpNameQBShowBricks = data->GetValueAsString(); } else if (data->GetKey() == u"spawner_name") { info.name = data->GetValueAsString(); } } } info.nodes.push_back(node); } info.templateID = path.spawner.spawnedLOT; info.spawnerID = path.spawner.spawnerObjID; info.respawnTime = path.spawner.respawnTime; info.amountMaintained = path.spawner.amountMaintained; info.maxToSpawn = path.spawner.maxToSpawn; info.activeOnLoad = path.spawner.spawnerNetActive; info.isNetwork = true; Spawner* spawner = new Spawner(info); Game::zoneManager->AddSpawner(info.spawnerID, spawner); } } } } else { LOG("Failed to open: %s", m_ZoneFilePath.c_str()); } m_ZonePath = m_ZoneFilePath.substr(0, m_ZoneFilePath.rfind('/') + 1); buffer.close(); } std::string Zone::GetFilePathForZoneID() { //We're gonna go ahead and presume we've got the db loaded already: CDZoneTableTable* zoneTable = CDClientManager::Instance().GetTable(); const CDZoneTable* zone = zoneTable->Query(this->GetZoneID().GetMapID()); if (zone != nullptr) { std::string toReturn = "maps/" + zone->zoneName; std::transform(toReturn.begin(), toReturn.end(), toReturn.begin(), ::tolower); return toReturn; } return std::string("ERR"); } //Based off code from: https://www.liquisearch.com/fletchers_checksum/implementation/optimizations uint32_t Zone::CalculateChecksum() { uint32_t sum1 = 0xffff, sum2 = 0xffff; for (std::map::const_iterator it = m_MapRevisions.cbegin(); it != m_MapRevisions.cend(); ++it) { uint32_t sceneID = it->first.GetSceneID(); sum2 += sum1 += (sceneID >> 16); sum2 += sum1 += (sceneID & 0xffff); uint32_t layerID = it->first.GetLayerID(); sum2 += sum1 += (layerID >> 16); sum2 += sum1 += (layerID & 0xffff); uint32_t revision = it->second; sum2 += sum1 += (revision >> 16); sum2 += sum1 += (revision & 0xffff); } sum1 = (sum1 & 0xffff) + (sum1 >> 16); sum2 = (sum2 & 0xffff) + (sum2 >> 16); return sum2 << 16 | sum1; } void Zone::LoadLevelsIntoMemory() { for (std::map::iterator it = m_Scenes.begin(); it != m_Scenes.end(); ++it) { if (it->second.level == nullptr) { it->second.level = new Level(this, m_ZonePath + it->second.filename); if (it->second.level->m_ChunkHeaders.size() > 0) { it->second.level->m_ChunkHeaders.begin()->second.lwoSceneID = it->first; AddRevision(it->second.level->m_ChunkHeaders.begin()->second.lwoSceneID, it->second.level->m_ChunkHeaders.begin()->second.fileInfo->revision); } } } } void Zone::AddRevision(LWOSCENEID sceneID, uint32_t revision) { for (std::pair item : m_MapRevisions) { if (item.first == sceneID) return; } m_MapRevisions[LWOSCENEID(sceneID)] = revision; } const void Zone::PrintAllGameObjects() { for (std::pair scene : m_Scenes) { LOG("In sceneID: %i", scene.first.GetSceneID()); scene.second.level->PrintAllObjects(); } } void Zone::LoadScene(std::istream& file) { SceneRef scene; scene.level = nullptr; LWOSCENEID lwoSceneID(LWOZONEID_INVALID, 0); uint8_t sceneFilenameLength; BinaryIO::BinaryRead(file, sceneFilenameLength); scene.filename = BinaryIO::ReadString(file, sceneFilenameLength); std::string luTriggersPath = scene.filename.substr(0, scene.filename.size() - 4) + ".lutriggers"; std::vector triggers; if(Game::assetManager->HasFile((m_ZonePath + luTriggersPath).c_str())) triggers = LoadLUTriggers(luTriggersPath, scene.id); for (LUTriggers::Trigger* trigger : triggers) { scene.triggers.insert({ trigger->id, trigger }); } if (m_FileFormatVersion >= Zone::FileFormatVersion::LatePreAlpha || m_FileFormatVersion < Zone::FileFormatVersion::PrePreAlpha) { BinaryIO::BinaryRead(file, scene.id); lwoSceneID.SetSceneID(scene.id); } if (m_FileFormatVersion >= Zone::FileFormatVersion::LatePreAlpha) { BinaryIO::BinaryRead(file, scene.sceneType); lwoSceneID.SetLayerID(scene.sceneType); uint8_t sceneNameLength; BinaryIO::BinaryRead(file, sceneNameLength); scene.name = BinaryIO::ReadString(file, sceneNameLength); } if (m_FileFormatVersion == Zone::FileFormatVersion::LatePreAlpha){ BinaryIO::BinaryRead(file, scene.unknown1); BinaryIO::BinaryRead(file, scene.unknown2); } if (m_FileFormatVersion >= Zone::FileFormatVersion::LatePreAlpha) { BinaryIO::BinaryRead(file, scene.color_r); BinaryIO::BinaryRead(file, scene.color_b); BinaryIO::BinaryRead(file, scene.color_g); } m_Scenes.insert(std::make_pair(lwoSceneID, scene)); m_NumberOfScenesLoaded++; } std::vector Zone::LoadLUTriggers(std::string triggerFile, LWOSCENEID sceneID) { std::vector lvlTriggers; auto buffer = Game::assetManager->GetFileAsBuffer((m_ZonePath + triggerFile).c_str()); if (!buffer.m_Success) { LOG("Failed to load %s from disk. Skipping loading triggers", (m_ZonePath + triggerFile).c_str()); return lvlTriggers; } std::istream file(&buffer); std::stringstream data; data << file.rdbuf(); buffer.close(); if (data.str().size() == 0) return lvlTriggers; tinyxml2::XMLDocument* doc = new tinyxml2::XMLDocument(); if (!doc) return lvlTriggers; if (doc->Parse(data.str().c_str(), data.str().size()) == 0) { //LOG("Loaded LUTriggers from file %s!", triggerFile.c_str()); } else { LOG("Failed to load LUTriggers from file %s", triggerFile.c_str()); return lvlTriggers; } tinyxml2::XMLElement* triggers = doc->FirstChildElement("triggers"); if (!triggers) return lvlTriggers; auto currentTrigger = triggers->FirstChildElement("trigger"); while (currentTrigger) { LUTriggers::Trigger* newTrigger = new LUTriggers::Trigger(); currentTrigger->QueryAttribute("enabled", &newTrigger->enabled); currentTrigger->QueryAttribute("id", &newTrigger->id); auto currentEvent = currentTrigger->FirstChildElement("event"); while (currentEvent) { LUTriggers::Event* newEvent = new LUTriggers::Event(); newEvent->id = TriggerEventType::StringToTriggerEventType(currentEvent->Attribute("id")); auto currentCommand = currentEvent->FirstChildElement("command"); while (currentCommand) { LUTriggers::Command* newCommand = new LUTriggers::Command(); newCommand->id = TriggerCommandType::StringToTriggerCommandType(currentCommand->Attribute("id")); newCommand->target = currentCommand->Attribute("target"); if (currentCommand->Attribute("targetName") != NULL) { newCommand->targetName = currentCommand->Attribute("targetName"); } if (currentCommand->Attribute("args") != NULL) { newCommand->args = currentCommand->Attribute("args"); } newEvent->commands.push_back(newCommand); currentCommand = currentCommand->NextSiblingElement("command"); } newTrigger->events.push_back(newEvent); currentEvent = currentEvent->NextSiblingElement("event"); } currentTrigger = currentTrigger->NextSiblingElement("trigger"); lvlTriggers.push_back(newTrigger); } delete doc; return lvlTriggers; } LUTriggers::Trigger* Zone::GetTrigger(uint32_t sceneID, uint32_t triggerID) { if (m_Scenes.find(sceneID) == m_Scenes.end()) return nullptr; if (m_Scenes[sceneID].triggers.find(triggerID) == m_Scenes[sceneID].triggers.end()) return nullptr; return m_Scenes[sceneID].triggers[triggerID]; } const Path* Zone::GetPath(std::string name) const { for (const auto& path : m_Paths) { if (name == path.pathName) { return &path; } } return nullptr; } void Zone::LoadSceneTransition(std::istream& file) { SceneTransition sceneTrans; if (m_FileFormatVersion < Zone::FileFormatVersion::Auramar) { uint8_t length; BinaryIO::BinaryRead(file, length); sceneTrans.name = BinaryIO::ReadString(file, length); BinaryIO::BinaryRead(file, sceneTrans.width); } //BR�THER MAY I HAVE SOME L��PS? uint8_t loops = (m_FileFormatVersion <= Zone::FileFormatVersion::LatePreAlpha || m_FileFormatVersion >= Zone::FileFormatVersion::Launch) ? 2 : 5; for (uint8_t i = 0; i < loops; ++i) { sceneTrans.points.push_back(LoadSceneTransitionInfo(file)); } m_SceneTransitions.push_back(sceneTrans); } SceneTransitionInfo Zone::LoadSceneTransitionInfo(std::istream& file) { SceneTransitionInfo info; BinaryIO::BinaryRead(file, info.sceneID); BinaryIO::BinaryRead(file, info.position); return info; } void Zone::LoadPath(std::istream& file) { Path path = Path(); BinaryIO::BinaryRead(file, path.pathVersion); uint8_t stringLength; BinaryIO::BinaryRead(file, stringLength); for (uint8_t i = 0; i < stringLength; ++i) { uint16_t character; BinaryIO::BinaryRead(file, character); path.pathName.push_back(character); } BinaryIO::BinaryRead(file, path.pathType); BinaryIO::BinaryRead(file, path.flags); BinaryIO::BinaryRead(file, path.pathBehavior); if (path.pathType == PathType::MovingPlatform) { if (path.pathVersion >= 18) { BinaryIO::BinaryRead(file, path.movingPlatform.timeBasedMovement); } else if (path.pathVersion >= 13) { uint8_t count; BinaryIO::BinaryRead(file, count); for (uint8_t i = 0; i < count; ++i) { uint16_t character; BinaryIO::BinaryRead(file, character); path.movingPlatform.platformTravelSound.push_back(character); } } } else if (path.pathType == PathType::Property) { BinaryIO::BinaryRead(file, path.property.pathType); BinaryIO::BinaryRead(file, path.property.price); BinaryIO::BinaryRead(file, path.property.rentalTime); BinaryIO::BinaryRead(file, path.property.associatedZone); if (path.pathVersion >= 5) { uint8_t count1; BinaryIO::BinaryRead(file, count1); for (uint8_t i = 0; i < count1; ++i) { uint16_t character; BinaryIO::BinaryRead(file, character); path.property.displayName.push_back(character); } uint32_t count2; BinaryIO::BinaryRead(file, count2); for (uint8_t i = 0; i < count2; ++i) { uint16_t character; BinaryIO::BinaryRead(file, character); path.property.displayDesc.push_back(character); } } if (path.pathVersion >= 6) BinaryIO::BinaryRead(file, path.property.type); if (path.pathVersion >= 7) { BinaryIO::BinaryRead(file, path.property.cloneLimit); BinaryIO::BinaryRead(file, path.property.repMultiplier); BinaryIO::BinaryRead(file, path.property.rentalPeriod); } if (path.pathVersion >= 8) { BinaryIO::BinaryRead(file, path.property.achievementRequired); BinaryIO::BinaryRead(file, path.property.playerZoneCoords); BinaryIO::BinaryRead(file, path.property.maxBuildHeight); } } else if (path.pathType == PathType::Camera) { uint8_t count; BinaryIO::BinaryRead(file, count); for (uint8_t i = 0; i < count; ++i) { uint16_t character; BinaryIO::BinaryRead(file, character); path.camera.nextPath.push_back(character); } if (path.pathVersion >= 14) { BinaryIO::BinaryRead(file, path.camera.rotatePlayer); } } else if (path.pathType == PathType::Spawner) { BinaryIO::BinaryRead(file, path.spawner.spawnedLOT); BinaryIO::BinaryRead(file, path.spawner.respawnTime); BinaryIO::BinaryRead(file, path.spawner.maxToSpawn); BinaryIO::BinaryRead(file, path.spawner.amountMaintained); BinaryIO::BinaryRead(file, path.spawner.spawnerObjID); BinaryIO::BinaryRead(file, path.spawner.spawnerNetActive); } // Read waypoints BinaryIO::BinaryRead(file, path.waypointCount); for (uint32_t i = 0; i < path.waypointCount; ++i) { PathWaypoint waypoint = PathWaypoint(); BinaryIO::BinaryRead(file, waypoint.position.x); BinaryIO::BinaryRead(file, waypoint.position.y); BinaryIO::BinaryRead(file, waypoint.position.z); if (path.pathType == PathType::Spawner || path.pathType == PathType::MovingPlatform || path.pathType == PathType::Race || path.pathType == PathType::Camera || path.pathType == PathType::Rail) { BinaryIO::BinaryRead(file, waypoint.rotation.w); BinaryIO::BinaryRead(file, waypoint.rotation.x); BinaryIO::BinaryRead(file, waypoint.rotation.y); BinaryIO::BinaryRead(file, waypoint.rotation.z); } if (path.pathType == PathType::MovingPlatform) { BinaryIO::BinaryRead(file, waypoint.movingPlatform.lockPlayer); BinaryIO::BinaryRead(file, waypoint.movingPlatform.speed); BinaryIO::BinaryRead(file, waypoint.movingPlatform.wait); if (path.pathVersion >= 13) { uint8_t count1; BinaryIO::BinaryRead(file, count1); for (uint8_t i = 0; i < count1; ++i) { uint16_t character; BinaryIO::BinaryRead(file, character); waypoint.movingPlatform.departSound.push_back(character); } uint8_t count2; BinaryIO::BinaryRead(file, count2); for (uint8_t i = 0; i < count2; ++i) { uint16_t character; BinaryIO::BinaryRead(file, character); waypoint.movingPlatform.arriveSound.push_back(character); } } } else if (path.pathType == PathType::Camera) { BinaryIO::BinaryRead(file, waypoint.camera.time); BinaryIO::BinaryRead(file, waypoint.camera.fov); BinaryIO::BinaryRead(file, waypoint.camera.tension); BinaryIO::BinaryRead(file, waypoint.camera.continuity); BinaryIO::BinaryRead(file, waypoint.camera.bias); } else if (path.pathType == PathType::Race) { BinaryIO::BinaryRead(file, waypoint.racing.isResetNode); BinaryIO::BinaryRead(file, waypoint.racing.isNonHorizontalCamera); BinaryIO::BinaryRead(file, waypoint.racing.planeWidth); BinaryIO::BinaryRead(file, waypoint.racing.planeHeight); BinaryIO::BinaryRead(file, waypoint.racing.shortestDistanceToEnd); } else if (path.pathType == PathType::Rail) { if (path.pathVersion > 16) BinaryIO::BinaryRead(file, waypoint.rail.speed); } // object LDF configs if (path.pathType == PathType::Movement || path.pathType == PathType::Spawner || path.pathType == PathType::Rail) { uint32_t count; BinaryIO::BinaryRead(file, count); for (uint32_t i = 0; i < count; ++i) { uint8_t count1; std::string parameter; std::string value; BinaryIO::BinaryRead(file, count1); for (uint8_t i = 0; i < count1; ++i) { uint16_t character; BinaryIO::BinaryRead(file, character); parameter.push_back(character); } uint8_t count2; BinaryIO::BinaryRead(file, count2); for (uint8_t i = 0; i < count2; ++i) { uint16_t character; BinaryIO::BinaryRead(file, character); value.push_back(character); } LDFBaseData* ldfConfig = nullptr; if (path.pathType == PathType::Movement || path.pathType == PathType::Rail) { ldfConfig = LDFBaseData::DataFromString(parameter + "=0:" + value); } else { ldfConfig = LDFBaseData::DataFromString(parameter + "=" + value); } if (ldfConfig) waypoint.config.push_back(ldfConfig); } } // We verify the waypoint heights against the navmesh because in many movement paths, // the waypoint is located near 0 height, if (path.pathType == PathType::Movement) { if (dpWorld::Instance().IsLoaded()) { // 2000 should be large enough for every world. waypoint.position.y = dpWorld::Instance().GetNavMesh()->GetHeightAtPoint(waypoint.position, 2000.0f); } } path.pathWaypoints.push_back(waypoint); } m_Paths.push_back(path); }