You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Code/CryEngine/CrySystem/LevelSystem/LevelSystem.cpp

1086 lines
37 KiB
C++

/*
* 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 <IAudioSystem.h>
#include "IMovieSystem.h"
#include "IMaterialEffects.h"
#include <IResourceManager.h>
#include <ILocalizationManager.h>
#include "IDeferredCollisionEvent.h"
#include "CryPath.h"
#include <Pak/CryPakUtils.h>
#include <LoadScreenBus.h>
#include <AzCore/Debug/AssetTracking.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzFramework/IO/FileOperations.h>
#include <AzFramework/Entity/GameEntityContextBus.h>
#include <AzFramework/Input/Buses/Requests/InputChannelRequestBus.h>
#include <AzFramework/Spawnable/RootSpawnableInterface.h>
#include "MainThreadRenderRequestBus.h"
#include <LyShine/ILyShine.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzCore/Script/ScriptSystemBus.h>
#ifdef WIN32
#include <CryWindows.h>
#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<AZ::IO::IArchive::MaxPath> 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<AZStd::string> 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<SLevelNameAutoComplete, AZStd::no_destruct<SLevelNameAutoComplete>> 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<AZStd::string>& 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<AZStd::string> 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<AZ::IO::IArchive::MaxPath> 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<CLevelInfo>::iterator it = m_levelInfos.begin(); it != m_levelInfos.end(); ++it)
{
if (!azstricmp(it->GetName(), levelName.c_str()))
{
return &(*it);
}
}
//////////////////////////////////////////////////////////////////////////
for (AZStd::vector<CLevelInfo>::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<ILevelSystemListener*>::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<ILevelSystemListener*>::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<Audio::eAMRT_PARSE_CONTROLS_DATA> 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<Audio::eAMRT_PARSE_PRELOADS_DATA> 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<Audio::eAMRT_PRELOAD_SINGLE_REQUEST> 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<char> 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<AZStd::vector<char>> 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<ILevelSystemListener*>::const_iterator it = m_listeners.begin(); it != m_listeners.end(); ++it)
{
(*it)->OnPrepareNextLevel(pLevelInfo->GetName());
}
}
//------------------------------------------------------------------------
void CLevelSystem::OnLevelNotFound(const char* levelName)
{
for (AZStd::vector<ILevelSystemListener*>::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<ILevelSystemListener*>::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<ILevelSystemListener*>::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<ILevelSystemListener*>::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<ILevelSystemListener*>::const_iterator it = m_listeners.begin(); it != m_listeners.end(); ++it)
{
(*it)->OnLoadingProgress(levelName, progressAmount);
}
}
//------------------------------------------------------------------------
void CLevelSystem::OnUnloadComplete(const char* levelName)
{
for (AZStd::vector<ILevelSystemListener*>::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<Audio::eAMRT_UNLOAD_AFCM_DATA_BY_SCOPE> 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<Audio::eAMRT_CLEAR_CONTROLS_DATA> oAMData2(Audio::eADS_LEVEL_SPECIFIC);
oAudioRequestData.pData = &oAMData2;
Audio::AudioSystemRequestBus::Broadcast(&Audio::AudioSystemRequestBus::Events::PushRequestBlocking, oAudioRequestData);
Audio::SAudioManagerRequestData<Audio::eAMRT_CLEAR_PRELOADS_DATA> 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