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.
998 lines
34 KiB
C++
998 lines
34 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 <ILocalizationManager.h>
|
|
#include "CryPath.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);
|
|
|
|
// 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<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)
|
|
{
|
|
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;
|
|
|
|
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("@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(), 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<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* 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);
|
|
|
|
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<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;
|
|
}
|
|
|
|
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();
|
|
|
|
// 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();
|
|
|
|
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
|