/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ // Original file Copyright Crytek GMBH or its affiliates, used under license. // [LYN-2376] Remove the entire file once legacy slice support is removed #include "CrySystem_precompiled.h" #include "LevelSystem.h" #include #include "IMovieSystem.h" #include "IMaterialEffects.h" #include #include #include "IDeferredCollisionEvent.h" #include "CryPath.h" #include #include #include #include #include #include #include #include #include "MainThreadRenderRequestBus.h" #include #include #include #include #include #ifdef WIN32 #include #endif namespace LegacyLevelSystem { static constexpr const char* ArchiveExtension = ".pak"; void CLevelInfo::GetMemoryUsage(ICrySizer* pSizer) const { pSizer->AddObject(m_levelName); pSizer->AddObject(m_levelPath); } ////////////////////////////////////////////////////////////////////////// bool CLevelInfo::OpenLevelPak() { LOADING_TIME_PROFILE_SECTION; bool usePrefabSystemForLevels = false; AzFramework::ApplicationRequests::Bus::BroadcastResult( usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemForLevelsEnabled); // The prefab system doesn't use level.pak if (usePrefabSystemForLevels) { return false; } AZStd::string levelpak(m_levelPath); levelpak += "/level.pak"; AZStd::fixed_string fullLevelPakPath; bool bOk = gEnv->pCryPak->OpenPack( levelpak.c_str(), m_isPak ? AZ::IO::IArchive::FLAGS_LEVEL_PAK_INSIDE_PAK : (unsigned)0, NULL, &fullLevelPakPath, false); m_levelPakFullPath.assign(fullLevelPakPath.c_str()); return bOk; } ////////////////////////////////////////////////////////////////////////// void CLevelInfo::CloseLevelPak() { LOADING_TIME_PROFILE_SECTION; bool usePrefabSystemForLevels = false; AzFramework::ApplicationRequests::Bus::BroadcastResult( usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemForLevelsEnabled); // The prefab system doesn't use level.pak if (usePrefabSystemForLevels) { return; } if (!m_levelPakFullPath.empty()) { gEnv->pCryPak->ClosePack(m_levelPakFullPath.c_str(), AZ::IO::IArchive::FLAGS_PATH_REAL); m_levelPakFullPath.clear(); } } ////////////////////////////////////////////////////////////////////////// bool CLevelInfo::ReadInfo() { bool usePrefabSystemForLevels = false; AzFramework::ApplicationRequests::Bus::BroadcastResult( usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemForLevelsEnabled); if (usePrefabSystemForLevels) { // Set up a default game type for legacy code. m_defaultGameTypeName = "Mission0"; return true; } AZStd::string levelPath(m_levelPath); AZStd::string xmlFile(levelPath); xmlFile += "/LevelInfo.xml"; XmlNodeRef rootNode = GetISystem()->LoadXmlFromFile(xmlFile.c_str()); if (rootNode) { AZStd::string dataFile(levelPath); dataFile += "/LevelDataAction.xml"; XmlNodeRef dataNode = GetISystem()->LoadXmlFromFile(dataFile.c_str()); if (!dataNode) { dataFile = levelPath + "/LevelData.xml"; dataNode = GetISystem()->LoadXmlFromFile(dataFile.c_str()); } if (dataNode) { XmlNodeRef gameTypesNode = dataNode->findChild("Missions"); if ((gameTypesNode != 0) && (gameTypesNode->getChildCount() > 0)) { m_defaultGameTypeName.clear(); for (int i = 0; i < gameTypesNode->getChildCount(); i++) { XmlNodeRef gameTypeNode = gameTypesNode->getChild(i); if (gameTypeNode->isTag("Mission")) { const char* gameTypeName = gameTypeNode->getAttr("Name"); if (gameTypeName) { m_defaultGameTypeName = gameTypeName; break; } } } } } } return rootNode != 0; } ////////////////////////////////////////////////////////////////////////// /// Used by console auto completion. struct SLevelNameAutoComplete : public IConsoleArgumentAutoComplete { AZStd::vector levels; virtual int GetCount() const { return levels.size(); }; virtual const char* GetValue(int nIndex) const { return levels[nIndex].c_str(); }; }; // definition and declaration must be separated for devirtualization static StaticInstance> g_LevelNameAutoComplete; //------------------------------------------------------------------------ static void LoadMap(IConsoleCmdArgs* args) { if (gEnv->pSystem && gEnv->pSystem->GetILevelSystem() && !gEnv->IsEditor()) { if (args->GetArgCount() > 1) { gEnv->pSystem->GetILevelSystem()->UnloadLevel(); gEnv->pSystem->GetILevelSystem()->LoadLevel(args->GetArg(1)); } } } //------------------------------------------------------------------------ static void UnloadMap([[maybe_unused]] IConsoleCmdArgs* args) { if (gEnv->pSystem && gEnv->pSystem->GetILevelSystem() && !gEnv->IsEditor()) { gEnv->pSystem->GetILevelSystem()->UnloadLevel(); } } //------------------------------------------------------------------------ CLevelSystem::CLevelSystem(ISystem* pSystem, const char* levelsFolder) : m_pSystem(pSystem) , m_pCurrentLevel(0) , m_pLoadingLevelInfo(0) { LOADING_TIME_PROFILE_SECTION; CRY_ASSERT(pSystem); //if (!gEnv->IsEditor()) Rescan(levelsFolder); m_fLastLevelLoadTime = 0; m_fLastTime = 0; m_bLevelLoaded = false; m_levelLoadStartTime.SetValue(0); m_nLoadedLevelsCount = 0; REGISTER_COMMAND("map", LoadMap, VF_BLOCKFRAME, "Load a map"); REGISTER_COMMAND("unload", UnloadMap, 0, "Unload current map"); gEnv->pConsole->RegisterAutoComplete("map", &(*g_LevelNameAutoComplete)); AZ_Assert(gEnv && gEnv->pCryPak, "gEnv and CryPak must be initialized for loading levels."); if (!gEnv || !gEnv->pCryPak) { return; } auto pPak = gEnv->pCryPak; if (AZ::IO::IArchive::LevelPackOpenEvent* levelPakOpenEvent = pPak->GetLevelPackOpenEvent()) { m_levelPackOpenHandler = AZ::IO::IArchive::LevelPackOpenEvent::Handler([this](const AZStd::vector& levelDirs) { for (AZStd::string dir : levelDirs) { AZ::StringFunc::Path::StripComponent(dir, true); AZStd::string searchPattern = dir + AZ_FILESYSTEM_SEPARATOR_WILDCARD; bool modFolder = false; PopulateLevels(searchPattern, dir, gEnv->pCryPak, modFolder, false); } }); m_levelPackOpenHandler.Connect(*levelPakOpenEvent); } if (AZ::IO::IArchive::LevelPackCloseEvent* levelPakCloseEvent = pPak->GetLevelPackCloseEvent()) { m_levelPackCloseHandler = AZ::IO::IArchive::LevelPackCloseEvent::Handler([this](AZStd::string_view) { Rescan(ILevelSystem::LevelsDirectoryName); }); m_levelPackCloseHandler.Connect(*levelPakCloseEvent); } } //------------------------------------------------------------------------ CLevelSystem::~CLevelSystem() { } //------------------------------------------------------------------------ void CLevelSystem::Rescan(const char* levelsFolder) { if (levelsFolder) { if (const ICmdLineArg* pModArg = m_pSystem->GetICmdLine()->FindArg(eCLAT_Pre, "MOD")) { if (m_pSystem->IsMODValid(pModArg->GetValue())) { m_levelsFolder.format("Mods/%s/%s", pModArg->GetValue(), levelsFolder); m_levelInfos.clear(); ScanFolder(0, true); } } m_levelsFolder = levelsFolder; } CRY_ASSERT(!m_levelsFolder.empty()); m_levelInfos.clear(); m_levelInfos.reserve(64); ScanFolder(0, false); g_LevelNameAutoComplete->levels.clear(); for (int i = 0; i < (int)m_levelInfos.size(); i++) { g_LevelNameAutoComplete->levels.push_back(AZStd::string(PathUtil::GetFileName(m_levelInfos[i].GetName()).c_str())); } } //----------------------------------------------------------------------- void CLevelSystem::ScanFolder(const char* subfolder, bool modFolder) { AZStd::string folder; if (subfolder && subfolder[0]) { folder = subfolder; } AZStd::string search(m_levelsFolder); if (!folder.empty()) { if (AZ::StringFunc::StartsWith(folder.c_str(), m_levelsFolder.c_str())) { search = folder; } else { search += "/" + folder; } } search += "/*"; AZ_Assert(gEnv && gEnv->pCryPak, "gEnv and must be initialized for loading levels."); if (!gEnv || !gEnv->pCryPak) { return; } auto pPak = gEnv->pCryPak; AZStd::unordered_set pakList; bool allowFileSystem = true; AZ::IO::ArchiveFileIterator handle = pPak->FindFirst(search.c_str(), 0, allowFileSystem); if (handle) { do { AZStd::string extension; AZStd::string levelName; AZ::StringFunc::Path::Split(handle.m_filename.data(), nullptr, nullptr, &levelName, &extension); if (extension == ArchiveExtension) { if (AZ::StringFunc::Equal(handle.m_filename.data(), LevelPakName)) { // level folder contain pak files like 'level.pak' // which we only want to load during level loading. continue; } AZStd::string levelContainerPakPath; AZ::StringFunc::Path::Join("@assets@", m_levelsFolder.c_str(), levelContainerPakPath); if (subfolder && subfolder[0]) { AZ::StringFunc::Path::Join(levelContainerPakPath.c_str(), subfolder, levelContainerPakPath); } AZ::StringFunc::Path::Join(levelContainerPakPath.c_str(), handle.m_filename.data(), levelContainerPakPath); pakList.emplace(levelContainerPakPath); continue; } } while (handle = pPak->FindNext(handle)); pPak->FindClose(handle); } // Open all the available paks found in the levels folder for (auto iter = pakList.begin(); iter != pakList.end(); iter++) { AZStd::fixed_string fullLevelPakPath; gEnv->pCryPak->OpenPack(iter->c_str(), (unsigned)0, nullptr, &fullLevelPakPath, false); } // Levels in bundles now take priority over levels outside of bundles. PopulateLevels(search, folder, pPak, modFolder, false); // Load levels outside of the bundles to maintain backward compatibility. PopulateLevels(search, folder, pPak, modFolder, true); } void CLevelSystem::PopulateLevels( AZStd::string searchPattern, AZStd::string& folder, AZ::IO::IArchive* pPak, bool& modFolder, bool fromFileSystemOnly) { { // allow this find first to actually touch the file system // (causes small overhead but with minimal amount of levels this should only be around 150ms on actual DVD Emu) AZ::IO::ArchiveFileIterator handle = pPak->FindFirst(searchPattern.c_str(), 0, fromFileSystemOnly); if (handle) { do { if ((handle.m_fileDesc.nAttrib & AZ::IO::FileDesc::Attribute::Subdirectory) != AZ::IO::FileDesc::Attribute::Subdirectory || handle.m_filename == "." || handle.m_filename == "..") { continue; } AZStd::string levelFolder; if (fromFileSystemOnly) { levelFolder = (folder.empty() ? "" : (folder + "/")) + AZStd::string(handle.m_filename.data(), handle.m_filename.size()); } else { AZStd::string levelName(AZ::IO::PathView(handle.m_filename).Filename().Native()); levelFolder = (folder.empty() ? "" : (folder + "/")) + levelName; } AZStd::string levelPath; if (AZ::StringFunc::StartsWith(levelFolder.c_str(), m_levelsFolder.c_str())) { levelPath = levelFolder; } else { levelPath = m_levelsFolder + "/" + levelFolder; } const AZStd::string levelPakName = levelPath + "/" + LevelPakName; const AZStd::string levelInfoName = levelPath + "/levelinfo.xml"; if (!pPak->IsFileExist( levelPakName.c_str(), fromFileSystemOnly ? AZ::IO::IArchive::eFileLocation_OnDisk : AZ::IO::IArchive::eFileLocation_InPak) && !pPak->IsFileExist( levelInfoName.c_str(), fromFileSystemOnly ? AZ::IO::IArchive::eFileLocation_OnDisk : AZ::IO::IArchive::eFileLocation_InPak)) { ScanFolder(levelFolder.c_str(), modFolder); continue; } // With the level.pak workflow, levelPath and levelName will point to a directory. // levelPath: levels/mylevel // levelName: mylevel CLevelInfo levelInfo; levelInfo.m_levelPath = levelPath; levelInfo.m_levelName = levelFolder; levelInfo.m_isPak = !fromFileSystemOnly; CLevelInfo* pExistingInfo = GetLevelInfoInternal(levelInfo.m_levelName); // Don't add the level if it is already in the list if (pExistingInfo == NULL) { m_levelInfos.push_back(levelInfo); } else { // Levels in bundles take priority over levels outside bundles. if (!pExistingInfo->m_isPak && levelInfo.m_isPak) { *pExistingInfo = levelInfo; } } } while (handle = pPak->FindNext(handle)); pPak->FindClose(handle); } } } //------------------------------------------------------------------------ int CLevelSystem::GetLevelCount() { return (int)m_levelInfos.size(); } //------------------------------------------------------------------------ ILevelInfo* CLevelSystem::GetLevelInfo(int level) { return GetLevelInfoInternal(level); } //------------------------------------------------------------------------ CLevelInfo* CLevelSystem::GetLevelInfoInternal(int level) { if ((level >= 0) && (level < GetLevelCount())) { return &m_levelInfos[level]; } return 0; } //------------------------------------------------------------------------ ILevelInfo* CLevelSystem::GetLevelInfo(const char* levelName) { return GetLevelInfoInternal(levelName); } //------------------------------------------------------------------------ CLevelInfo* CLevelSystem::GetLevelInfoInternal(const AZStd::string& levelName) { // If level not found by full name try comparing with only filename for (AZStd::vector::iterator it = m_levelInfos.begin(); it != m_levelInfos.end(); ++it) { if (!azstricmp(it->GetName(), levelName.c_str())) { return &(*it); } } ////////////////////////////////////////////////////////////////////////// for (AZStd::vector::iterator it = m_levelInfos.begin(); it != m_levelInfos.end(); ++it) { { if (!azstricmp(PathUtil::GetFileName(it->GetName()), levelName.c_str())) { return &(*it); } } } // Try stripping out the folder to find the raw filename AZStd::string sLevelName(levelName); size_t lastSlash = sLevelName.find_last_of('\\'); if (lastSlash == AZStd::string::npos) { lastSlash = sLevelName.find_last_of('/'); } if (lastSlash != AZStd::string::npos) { sLevelName = sLevelName.substr(lastSlash + 1, sLevelName.size() - lastSlash - 1); return GetLevelInfoInternal(sLevelName.c_str()); } return 0; } //------------------------------------------------------------------------ void CLevelSystem::AddListener(ILevelSystemListener* pListener) { AZStd::vector::iterator it = AZStd::find(m_listeners.begin(), m_listeners.end(), pListener); if (it == m_listeners.end()) { m_listeners.reserve(12); m_listeners.push_back(pListener); } } //------------------------------------------------------------------------ void CLevelSystem::RemoveListener(ILevelSystemListener* pListener) { AZStd::vector::iterator it = AZStd::find(m_listeners.begin(), m_listeners.end(), pListener); if (it != m_listeners.end()) { m_listeners.erase(it); if (m_listeners.empty()) { m_listeners.shrink_to_fit(); } } } //------------------------------------------------------------------------ bool CLevelSystem::LoadLevel(const char* _levelName) { if (gEnv->IsEditor()) { AZ_TracePrintf("CrySystem::CLevelSystem", "LoadLevel for %s was called in the editor - not actually loading.\n", _levelName); return false; } // If a level is currently loaded, unload it before loading the next one. if (IsLevelLoaded()) { UnloadLevel(); } gEnv->pSystem->GetISystemEventDispatcher()->OnSystemEvent(ESYSTEM_EVENT_LEVEL_LOAD_PREPARE, 0, 0); PrepareNextLevel(_levelName); ILevel* level = LoadLevelInternal(_levelName); if (level) { OnLoadingComplete(_levelName); } return (level != nullptr); } //------------------------------------------------------------------------ ILevel* CLevelSystem::LoadLevelInternal(const char* _levelName) { gEnv->pSystem->SetSystemGlobalState(ESYSTEM_GLOBAL_STATE_LEVEL_LOAD_START); AZ_ASSET_NAMED_SCOPE("Level: %s", _levelName); CryLog ("Level system is loading \"%s\"", _levelName); INDENT_LOG_DURING_SCOPE(); char levelName[256]; cry_strcpy(levelName, _levelName); // Not remove a scope!!! { LOADING_TIME_PROFILE_SECTION; //m_levelLoadStartTime = gEnv->pTimer->GetAsyncTime(); CLevelInfo* pLevelInfo = GetLevelInfoInternal(levelName); if (!pLevelInfo) { // alert the listener OnLevelNotFound(levelName); return 0; } m_bLevelLoaded = false; const bool bLoadingSameLevel = azstricmp(m_lastLevelName.c_str(), levelName) == 0; m_lastLevelName = levelName; delete m_pCurrentLevel; CLevel* pLevel = new CLevel(); pLevel->m_levelInfo = *pLevelInfo; m_pCurrentLevel = pLevel; ////////////////////////////////////////////////////////////////////////// // Read main level info. if (!pLevelInfo->ReadInfo()) { OnLoadingError(levelName, "Failed to read level info (level.pak might be corrupted)!"); return 0; } //[AlexMcC|19.04.10]: Update the level's LevelInfo pLevel->m_levelInfo = *pLevelInfo; ////////////////////////////////////////////////////////////////////////// gEnv->pConsole->SetScrollMax(600); ICVar* con_showonload = gEnv->pConsole->GetCVar("con_showonload"); if (con_showonload && con_showonload->GetIVal() != 0) { gEnv->pConsole->ShowConsole(true); ICVar* g_enableloadingscreen = gEnv->pConsole->GetCVar("g_enableloadingscreen"); if (g_enableloadingscreen) { g_enableloadingscreen->Set(0); } } // Reset the camera to (1,1,1) (not (0,0,0) which is the invalid/uninitialised state, // to avoid the hack in the renderer to not show anything if the camera is at the origin). CCamera defaultCam; defaultCam.SetPosition(Vec3(1.0f)); m_pSystem->SetViewCamera(defaultCam); m_pLoadingLevelInfo = pLevelInfo; OnLoadingStart(levelName); auto pPak = gEnv->pCryPak; AZStd::string levelPath(pLevelInfo->GetPath()); /* ICVar *pFileCache = gEnv->pConsole->GetCVar("sys_FileCache"); CRY_ASSERT(pFileCache); if(pFileCache->GetIVal()) { if(pPak->OpenPack("",pLevelInfo->GetPath()+string("/FileCache.dat"))) gEnv->pLog->Log("FileCache.dat loaded"); else gEnv->pLog->Log("FileCache.dat not loaded"); } */ m_pSystem->SetThreadState(ESubsys_Physics, false); ICVar* pSpamDelay = gEnv->pConsole->GetCVar("log_SpamDelay"); float spamDelay = 0.0f; if (pSpamDelay) { spamDelay = pSpamDelay->GetFVal(); pSpamDelay->Set(0.0f); } // Parse level specific config data. AZStd::string const sLevelNameOnly(PathUtil::GetFileName(levelName)); if (!sLevelNameOnly.empty()) { const char* controlsPath = nullptr; Audio::AudioSystemRequestBus::BroadcastResult(controlsPath, &Audio::AudioSystemRequestBus::Events::GetControlsPath); if (controlsPath) { AZStd::string sAudioLevelPath(controlsPath); sAudioLevelPath.append("levels/"); sAudioLevelPath += sLevelNameOnly; Audio::SAudioManagerRequestData oAMData(sAudioLevelPath.c_str(), Audio::eADS_LEVEL_SPECIFIC); Audio::SAudioRequest oAudioRequestData; oAudioRequestData.nFlags = (Audio::eARF_PRIORITY_HIGH | Audio::eARF_EXECUTE_BLOCKING); // Needs to be blocking so data is available for next preloading request! oAudioRequestData.pData = &oAMData; Audio::AudioSystemRequestBus::Broadcast(&Audio::AudioSystemRequestBus::Events::PushRequestBlocking, oAudioRequestData); Audio::SAudioManagerRequestData oAMData2(sAudioLevelPath.c_str(), Audio::eADS_LEVEL_SPECIFIC); oAudioRequestData.pData = &oAMData2; Audio::AudioSystemRequestBus::Broadcast(&Audio::AudioSystemRequestBus::Events::PushRequestBlocking, oAudioRequestData); Audio::TAudioPreloadRequestID nPreloadRequestID = INVALID_AUDIO_PRELOAD_REQUEST_ID; Audio::AudioSystemRequestBus::BroadcastResult(nPreloadRequestID, &Audio::AudioSystemRequestBus::Events::GetAudioPreloadRequestID, sLevelNameOnly.c_str()); if (nPreloadRequestID != INVALID_AUDIO_PRELOAD_REQUEST_ID) { Audio::SAudioManagerRequestData requestData(nPreloadRequestID, true); oAudioRequestData.pData = &requestData; Audio::AudioSystemRequestBus::Broadcast(&Audio::AudioSystemRequestBus::Events::PushRequestBlocking, oAudioRequestData); } } } { AZStd::string missionXml("Mission_"); missionXml += pLevelInfo->m_defaultGameTypeName; missionXml += ".xml"; AZStd::string xmlFile(pLevelInfo->GetPath()); xmlFile += "/"; xmlFile += missionXml; if (!gEnv->IsEditor()) { AZStd::string entitiesFilename = AZStd::string::format("%s/%s.entities_xml", pLevelInfo->GetPath(), pLevelInfo->m_defaultGameTypeName.c_str()); AZStd::vector fileBuffer; CCryFile entitiesFile; if (entitiesFile.Open(entitiesFilename.c_str(), "rt")) { fileBuffer.resize(entitiesFile.GetLength()); if (fileBuffer.size() == entitiesFile.ReadRaw(fileBuffer.begin(), fileBuffer.size())) { AZ::IO::ByteContainerStream> fileStream(&fileBuffer); EBUS_EVENT(AzFramework::GameEntityContextRequestBus, LoadFromStream, fileStream, false); } } } } ////////////////////////////////////////////////////////////////////////// // Movie system must be reset after entities. ////////////////////////////////////////////////////////////////////////// IMovieSystem* movieSys = gEnv->pMovieSystem; if (movieSys != NULL) { // bSeekAllToStart needs to be false here as it's only of interest in the editor movieSys->Reset(true, false); } gEnv->pSystem->SetSystemGlobalState(ESYSTEM_GLOBAL_STATE_LEVEL_LOAD_START_PRECACHE); ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// gEnv->pConsole->SetScrollMax(600 / 2); pPak->GetResourceList(AZ::IO::IArchive::RFOM_NextLevel)->Clear(); if (pSpamDelay) { pSpamDelay->Set(spamDelay); } m_bLevelLoaded = true; gEnv->pSystem->SetSystemGlobalState(ESYSTEM_GLOBAL_STATE_LEVEL_LOAD_END); } GetISystem()->GetISystemEventDispatcher()->OnSystemEvent(ESYSTEM_EVENT_LEVEL_LOAD_END, 0, 0); if (auto cvar = gEnv->pConsole->GetCVar("sv_map"); cvar) { cvar->Set(levelName); } gEnv->pSystem->GetISystemEventDispatcher()->OnSystemEvent(ESYSTEM_EVENT_LEVEL_PRECACHE_START, 0, 0); m_pSystem->SetThreadState(ESubsys_Physics, true); return m_pCurrentLevel; } //------------------------------------------------------------------------ void CLevelSystem::PrepareNextLevel(const char* levelName) { CLevelInfo* pLevelInfo = GetLevelInfoInternal(levelName); if (!pLevelInfo) { // alert the listener OnLevelNotFound(levelName); return; } // This work not required in-editor. if (!gEnv || !gEnv->IsEditor()) { m_levelLoadStartTime = gEnv->pTimer->GetAsyncTime(); // Open pak file for a new level. pLevelInfo->OpenLevelPak(); // switched to level heap, so now imm start the loading screen (renderer will be reinitialized in the levelheap) gEnv->pSystem->GetISystemEventDispatcher()->OnSystemEvent(ESYSTEM_EVENT_LEVEL_LOAD_START_LOADINGSCREEN, 0, 0); gEnv->pSystem->SetSystemGlobalState(ESYSTEM_GLOBAL_STATE_LEVEL_LOAD_START_PREPARE); // Inform resource manager about loading of the new level. GetISystem()->GetIResourceManager()->PrepareLevel(pLevelInfo->GetPath(), pLevelInfo->GetName()); } for (AZStd::vector::const_iterator it = m_listeners.begin(); it != m_listeners.end(); ++it) { (*it)->OnPrepareNextLevel(pLevelInfo->GetName()); } } //------------------------------------------------------------------------ void CLevelSystem::OnLevelNotFound(const char* levelName) { for (AZStd::vector::const_iterator it = m_listeners.begin(); it != m_listeners.end(); ++it) { (*it)->OnLevelNotFound(levelName); } } //------------------------------------------------------------------------ void CLevelSystem::OnLoadingStart(const char* levelName) { if (gEnv->pCryPak->GetRecordFileOpenList() == AZ::IO::IArchive::RFOM_EngineStartup) { gEnv->pCryPak->RecordFileOpen(AZ::IO::IArchive::RFOM_Level); } m_fLastTime = gEnv->pTimer->GetAsyncCurTime(); GetISystem()->GetISystemEventDispatcher()->OnSystemEvent(ESYSTEM_EVENT_LEVEL_LOAD_START, 0, 0); LOADING_TIME_PROFILE_SECTION(gEnv->pSystem); for (AZStd::vector::const_iterator it = m_listeners.begin(); it != m_listeners.end(); ++it) { (*it)->OnLoadingStart(levelName); } } //------------------------------------------------------------------------ void CLevelSystem::OnLoadingError(const char* levelName, const char* error) { ILevelInfo* pLevelInfo = m_pLoadingLevelInfo; if (!pLevelInfo) { CRY_ASSERT(false); return; } if (gEnv->pRenderer) { gEnv->pRenderer->SetTexturePrecaching(false); } for (AZStd::vector::const_iterator it = m_listeners.begin(); it != m_listeners.end(); ++it) { (*it)->OnLoadingError(levelName, error); } ((CLevelInfo*)pLevelInfo)->CloseLevelPak(); } //------------------------------------------------------------------------ void CLevelSystem::OnLoadingComplete(const char* levelName) { CTimeValue t = gEnv->pTimer->GetAsyncTime(); m_fLastLevelLoadTime = (t - m_levelLoadStartTime).GetSeconds(); LogLoadingTime(); m_nLoadedLevelsCount++; // Hide console after loading. gEnv->pConsole->ShowConsole(false); for (AZStd::vector::const_iterator it = m_listeners.begin(); it != m_listeners.end(); ++it) { (*it)->OnLoadingComplete(levelName); } #if AZ_LOADSCREENCOMPONENT_ENABLED EBUS_EVENT(LoadScreenBus, Stop); #endif // if AZ_LOADSCREENCOMPONENT_ENABLED } //------------------------------------------------------------------------ void CLevelSystem::OnLoadingProgress(const char* levelName, int progressAmount) { for (AZStd::vector::const_iterator it = m_listeners.begin(); it != m_listeners.end(); ++it) { (*it)->OnLoadingProgress(levelName, progressAmount); } } //------------------------------------------------------------------------ void CLevelSystem::OnUnloadComplete(const char* levelName) { for (AZStd::vector::const_iterator it = m_listeners.begin(); it != m_listeners.end(); ++it) { (*it)->OnUnloadComplete(levelName); } } ////////////////////////////////////////////////////////////////////////// void CLevelSystem::LogLoadingTime() { if (gEnv->IsEditor()) { return; } if (!GetISystem()->IsDevMode()) { return; } char vers[128]; GetISystem()->GetFileVersion().ToString(vers, sizeof(vers)); const char* sChain = ""; if (m_nLoadedLevelsCount > 0) { sChain = " (Chained)"; } AZStd::string text; text.format("Game Level Load Time: [%s] Level %s loaded in %.2f seconds%s", vers, m_lastLevelName.c_str(), m_fLastLevelLoadTime, sChain); gEnv->pLog->Log(text.c_str()); } void CLevelSystem::GetMemoryUsage(ICrySizer* pSizer) const { pSizer->AddObject(this, sizeof(*this)); pSizer->AddObject(m_levelInfos); pSizer->AddObject(m_levelsFolder); pSizer->AddObject(m_listeners); } ////////////////////////////////////////////////////////////////////////// void CLevelSystem::UnloadLevel() { if (gEnv->IsEditor()) { return; } if (!m_pLoadingLevelInfo) { return; } CryLog("UnloadLevel Start"); INDENT_LOG_DURING_SCOPE(); // Flush core buses. We're about to unload Cry modules and need to ensure we don't have module-owned functions left behind. AZ::Data::AssetBus::ExecuteQueuedEvents(); AZ::TickBus::ExecuteQueuedEvents(); AZ::MainThreadRenderRequestBus::ExecuteQueuedEvents(); if (gEnv && gEnv->pSystem) { // clear all error messages to prevent stalling due to runtime file access check during chainloading gEnv->pSystem->ClearErrorMessages(); } if (gEnv && gEnv->pCryPak) { gEnv->pCryPak->DisableRuntimeFileAccess(false); } CTimeValue tBegin = gEnv->pTimer->GetAsyncTime(); //AM: Flush render thread (Flush is not exposed - using EndFrame()) //We are about to delete resources that could be in use if (gEnv->pRenderer) { gEnv->pRenderer->EndFrame(); bool isLoadScreenPlaying = false; #if AZ_LOADSCREENCOMPONENT_ENABLED LoadScreenBus::BroadcastResult(isLoadScreenPlaying, &LoadScreenBus::Events::IsPlaying); #endif // if AZ_LOADSCREENCOMPONENT_ENABLED // force a black screen as last render command. //if load screen is playing do not call this draw as it may lead to a crash due to UI loading code getting //pumped while loading the shaders for this draw. if (!isLoadScreenPlaying) { gEnv->pRenderer->BeginFrame(); gEnv->pRenderer->SetState(GS_BLSRC_SRCALPHA | GS_BLDST_ONEMINUSSRCALPHA | GS_NODEPTHTEST); gEnv->pRenderer->Draw2dImage(0, 0, 800, 600, -1, 0.0f, 0.0f, 1.0f, 1.0f, 0.f, 0.0f, 0.0f, 0.0f, 1.0, 0.f); gEnv->pRenderer->EndFrame(); } //flush any outstanding texture requests gEnv->pRenderer->FlushPendingTextureTasks(); } // Clear level entities and prefab instances. EBUS_EVENT(AzFramework::GameEntityContextRequestBus, ResetGameContext); if (gEnv->pMovieSystem) { gEnv->pMovieSystem->Reset(false, false); gEnv->pMovieSystem->RemoveAllSequences(); } // Unload level specific audio binary data. Audio::SAudioManagerRequestData oAMData(Audio::eADS_LEVEL_SPECIFIC); Audio::SAudioRequest oAudioRequestData; oAudioRequestData.nFlags = (Audio::eARF_PRIORITY_HIGH | Audio::eARF_EXECUTE_BLOCKING); oAudioRequestData.pData = &oAMData; Audio::AudioSystemRequestBus::Broadcast(&Audio::AudioSystemRequestBus::Events::PushRequestBlocking, oAudioRequestData); // Now unload level specific audio config data. Audio::SAudioManagerRequestData oAMData2(Audio::eADS_LEVEL_SPECIFIC); oAudioRequestData.pData = &oAMData2; Audio::AudioSystemRequestBus::Broadcast(&Audio::AudioSystemRequestBus::Events::PushRequestBlocking, oAudioRequestData); Audio::SAudioManagerRequestData oAMData3(Audio::eADS_LEVEL_SPECIFIC); oAudioRequestData.pData = &oAMData3; Audio::AudioSystemRequestBus::Broadcast(&Audio::AudioSystemRequestBus::Events::PushRequestBlocking, oAudioRequestData); // Reset the camera to (0,0,0) which is the invalid/uninitialised state CCamera defaultCam; m_pSystem->SetViewCamera(defaultCam); OnUnloadComplete(m_lastLevelName.c_str()); // -- kenzo: this will close all pack files for this level // (even the ones which were not added through here, if this is not desired, // then change code to close only level.pak) if (m_pLoadingLevelInfo) { ((CLevelInfo*)m_pLoadingLevelInfo)->CloseLevelPak(); m_pLoadingLevelInfo = NULL; } m_lastLevelName.clear(); GetISystem()->GetIResourceManager()->UnloadLevel(); SAFE_RELEASE(m_pCurrentLevel); // Force Lua garbage collection (may no longer be needed now the legacy renderer has been removed). // Normally the GC step is triggered at the end of this method (by the ESYSTEM_EVENT_LEVEL_POST_UNLOAD event). EBUS_EVENT(AZ::ScriptSystemRequestBus, GarbageCollect); // Force to clean render resources left after deleting all objects and materials. IRenderer* pRenderer = gEnv->pRenderer; if (pRenderer) { pRenderer->FlushRTCommands(true, true, true); CryComment("Deleting Render meshes, render resources and flush texture streaming"); // This may also release some of the materials. int flags = FRR_DELETED_MESHES | FRR_FLUSH_TEXTURESTREAMING | FRR_OBJECTS | FRR_RENDERELEMENTS | FRR_RP_BUFFERS | FRR_POST_EFFECTS; // Always keep the system resources around in the editor. // If a level load fails for any reason, then do not unload the system resources, otherwise we will not have any system resources to continue rendering the console and debug output text. if (!gEnv->IsEditor() && !GetLevelLoadFailed()) { flags |= FRR_SYSTEM_RESOURCES; } pRenderer->FreeResources(flags); CryComment("done"); } // Perform level unload procedures for the LyShine UI system if (gEnv && gEnv->pLyShine) { gEnv->pLyShine->OnLevelUnload(); } m_bLevelLoaded = false; CTimeValue tUnloadTime = gEnv->pTimer->GetAsyncTime() - tBegin; CryLog("UnloadLevel End: %.1f sec", tUnloadTime.GetSeconds()); // Must be sent last. // Cleanup all containers GetISystem()->GetISystemEventDispatcher()->OnSystemEvent(ESYSTEM_EVENT_LEVEL_POST_UNLOAD, 0, 0); AzFramework::InputChannelRequestBus::Broadcast(&AzFramework::InputChannelRequests::ResetState); } } // namespace LegacyLevelSystem