/* * Copyright (c) Contributors to the Open 3D Engine Project. * For complete copyright and license terms please see the LICENSE at the root of this distribution. * * SPDX-License-Identifier: Apache-2.0 OR MIT * */ // [LYN-2376] Remove the entire file once legacy slice support is removed #include "CrySystem_precompiled.h" #include "LevelSystem.h" #include "IMovieSystem.h" #include #include "CryPath.h" #include #include #include #include #include #include #include #include #include "MainThreadRenderRequestBus.h" #include #include #include #include #include namespace LegacyLevelSystem { static constexpr const char* ArchiveExtension = ".pak"; ////////////////////////////////////////////////////////////////////////// bool CLevelInfo::OpenLevelPak() { bool usePrefabSystemForLevels = false; AzFramework::ApplicationRequests::Bus::BroadcastResult( usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemForLevelsEnabled); // The prefab system doesn't use level.pak if (usePrefabSystemForLevels) { return false; } AZ::IO::Path levelPak(m_levelPath); levelPak /= "level.pak"; AZ::IO::FixedMaxPathString fullLevelPakPath; bool bOk = gEnv->pCryPak->OpenPack(levelPak.Native(), nullptr, &fullLevelPakPath, false); m_levelPakFullPath.assign(fullLevelPakPath.c_str(), fullLevelPakPath.size()); return bOk; } ////////////////////////////////////////////////////////////////////////// void CLevelInfo::CloseLevelPak() { 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()); m_levelPakFullPath.clear(); } } ////////////////////////////////////////////////////////////////////////// bool CLevelInfo::ReadInfo() { bool usePrefabSystemForLevels = false; AzFramework::ApplicationRequests::Bus::BroadcastResult( usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemForLevelsEnabled); // Set up a default game type for legacy code. m_defaultGameTypeName = "Mission0"; if (usePrefabSystemForLevels) { 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 static_cast(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) { 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() { UnloadLevel(); } //------------------------------------------------------------------------ void CLevelSystem::Rescan(const char* levelsFolder) { if (levelsFolder) { 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; AZ::IO::ArchiveFileIterator handle = pPak->FindFirst(search.c_str(), AZ::IO::IArchive::eFileSearchType_AllowOnDiskOnly); 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("@products@", 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++) { gEnv->pCryPak->OpenPack(iter->c_str(), nullptr, nullptr, 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(), AZ::IO::IArchive::eFileSearchType_AllowOnDiskOnly); 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()).c_str(), 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]; azstrcpy(levelName, AZ_ARRAY_SIZE(levelName), _levelName); // Not remove a scope!!! { //m_levelLoadStartTime = gEnv->pTimer->GetAsyncTime(); CLevelInfo* pLevelInfo = GetLevelInfoInternal(levelName); if (!pLevelInfo) { // alert the listener OnLevelNotFound(levelName); return 0; } m_bLevelLoaded = false; 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); } } m_pLoadingLevelInfo = pLevelInfo; OnLoadingStart(levelName); auto pPak = gEnv->pCryPak; AZStd::string levelPath(pLevelInfo->GetPath()); ICVar* pSpamDelay = gEnv->pConsole->GetCVar("log_SpamDelay"); float spamDelay = 0.0f; if (pSpamDelay) { spamDelay = pSpamDelay->GetFVal(); pSpamDelay->Set(0.0f); } { 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); 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); } 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); 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; } 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)"; } gEnv->pLog->Log("Game Level Load Time: [%s] Level %s loaded in %.2f seconds%s", vers, m_lastLevelName.c_str(), m_fLastLevelLoadTime, sChain); } ////////////////////////////////////////////////////////////////////////// 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(); // Clear level entities and prefab instances. EBUS_EVENT(AzFramework::GameEntityContextRequestBus, ResetGameContext); if (gEnv->pMovieSystem) { gEnv->pMovieSystem->Reset(false, false); gEnv->pMovieSystem->RemoveAllSequences(); } 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(); 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); // 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