56904d1799
* Generic Cleanup Removals: * Unused cvars,member variables and defines * Windows media center edition support routines * CSystem - removed debug_GetCallStackRaw/GetRootWindowMessageHandler/ UnloadDLL/ShutdownModuleLibraries Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com> * Fix CryMessageBox return values in Windows build Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com> * Remove named selection group and a few smaller unused functionalities Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com> * Remove ObjectManager export functionality + 2 deprecated functions Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com> * Remove object legacy freeze/hide support from ObjectManager Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com> * Remove ObjectManager duplicate name dection, as well as object renaming Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com> * Remove ObjectManager serialization and selection callbacks Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com> * ObjectManager - remove MoveObjects/HitTestObject/EndEditParams and related members/functions Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com> * Remove a bunch of unused clone related functionality + misc Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com> * ObjectManager - misc removals Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com> * ObjectManager - more removals Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com> * Remove unused object selection/retrieval in ObjectManager and LoadObjects in ObjectLoader Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com> * CBaseObject - remove unused material layers mask support Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com> * More CBaseObject cleanups. Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com> * CBaseObject - remove SubObj functions and IMouseCreateCallback Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com> * Remove unused procedural floor management, helper scale and tags. Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com> * CBaseObject - remove more unused methods. Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com> * Removals in multiple places. CBaseObject - unused `OBJTYPE_DUMMY` flag removed, member `IsSameClass` CObjectArchive removed unused methods/members CObjectManager removed unused `Update` method Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com> * More removals IDisplayViewport - HitTestLine/GetGridStep/setHitcontext/ GetConstructionPlane Unused Cry_Matrix44 template specializations. Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com> * O3DE.exe Project-Centric "Open Editor" fix (#5852) * The O3DE.exe Open Editor button now attempts to open the Editor in the build directory of the project being opened. If their is no Editor within the build directory of the Project, it uses the Editor.exe in the current O3DE.exe executable directory if it exists Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Engine .gitignore now ignores the build directory if placed in the AutomatedTesting project Previously it was just ignoring a `[Bb]uild` directory if it was directly within the engine root. This change matches the behavior of the project templates. Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Renamed the ProjectUtils GetEditorDirectory function to GetEditorExecutablePath Added a platform specific implementation for retrieving the path to the Editor executable in the GetEditorExectuablePath function. It first attempts to locate the Editor via checking the project build directory for an Editor executable before falling back to checking the binary directory of the currently running O3DE executable. Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> * Correct the MacOS GetEditorExecutablePath to return the Editor path Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com> * Adding missing C++20 std::erase implementations (#5735) There were already implementations for std::erase_if. This adds the counterpart AZStd::erase versions resolves #5734 Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com> * More removals and refactors `ShowMessage` and `CryMessageBox` return void now Simplify code in `CSystem::WarningV` Remove unused `CryGetTicksPerSec` Remove unused WinBase functionality Replace `nanosleep` call in WinBase with `std::this_thread::sleep_for` Remove unused Win32Wrapper routines Remove unused IFunctorBase.h and IEntityObjectListener.h Fix VectorAndArray.cpp compilation Use QMessageBox instead of CryMessageBox in the editor. Remove empty ArchiveVars platform specific files Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com> * Fix test code. Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com> * Remove an unused function Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com> * CBaseObject and undo description removals. Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com> * A bunch of removals * Remove IRenderNode * Remove editor's KDTree * Remove StatObjBus, InstanceStatObjEventBus::ReleaseData is redundant ( same functionality in the only available handler is triggered by `OnCryEditorCloseScene`) * Remove CExportManager::AddStatObj/AddMeshes/AddMesh, IExportManager::ExportSingleStatObj * Remove CIconManager/IIconManager::GetObject * Remove CBaseObject::IntersectRayMesh * Remove IIndexedMesh and related structs. * Unused IUndoObject::GetDescription and all derived implementations. * Unused CUndoBaseLibrary/CUndoBaseLibraryManager * Unused Matrix34_tpl typedefs * Legacy Xml classes cleanup Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com> * Remove unused `Vec3ToVector3D` helper Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com> * fix - remove unused static variable Signed-off-by: nemerle <96597+nemerle@users.noreply.github.com> Co-authored-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>
2226 lines
75 KiB
C++
2226 lines
75 KiB
C++
/*
|
|
* 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
|
|
*
|
|
*/
|
|
|
|
|
|
#include "EditorDefs.h"
|
|
|
|
#include "CryEditDoc.h"
|
|
|
|
// Qt
|
|
#include <QDateTime>
|
|
#include <QDialogButtonBox>
|
|
|
|
// AzCore
|
|
#include <AzCore/Component/TransformBus.h>
|
|
#include <AzCore/Asset/AssetManager.h>
|
|
#include <AzCore/Interface/Interface.h>
|
|
#include <AzCore/Time/ITime.h>
|
|
#include <AzCore/Utils/Utils.h>
|
|
#include <MathConversion.h>
|
|
|
|
// AzFramework
|
|
#include <AzFramework/Archive/IArchive.h>
|
|
#include <AzFramework/API/ApplicationAPI.h>
|
|
|
|
// AzToolsFramework
|
|
#include <AzToolsFramework/Slice/SliceUtilities.h>
|
|
#include <AzToolsFramework/UI/UICore/WidgetHelpers.h>
|
|
#include <AzToolsFramework/UI/Layer/NameConflictWarning.hxx>
|
|
#include <AzToolsFramework/API/EditorLevelNotificationBus.h>
|
|
|
|
// Editor
|
|
#include "Settings.h"
|
|
|
|
#include "PluginManager.h"
|
|
#include "ViewManager.h"
|
|
#include "DisplaySettings.h"
|
|
#include "GameEngine.h"
|
|
|
|
#include "CryEdit.h"
|
|
#include "ActionManager.h"
|
|
#include "Include/IObjectManager.h"
|
|
#include "ErrorReportDialog.h"
|
|
#include "SurfaceTypeValidator.h"
|
|
#include "Util/AutoLogTime.h"
|
|
#include "CheckOutDialog.h"
|
|
#include "GameExporter.h"
|
|
#include "MainWindow.h"
|
|
#include "LevelFileDialog.h"
|
|
#include "Undo/Undo.h"
|
|
|
|
#include <Atom/RPI.Public/ViewportContext.h>
|
|
#include <Atom/RPI.Public/ViewportContextBus.h>
|
|
|
|
// LmbrCentral
|
|
#include <LmbrCentral/Audio/AudioSystemComponentBus.h>
|
|
#include <LmbrCentral/Rendering/EditorLightComponentBus.h> // for LmbrCentral::EditorLightComponentRequestBus
|
|
|
|
static const char* kAutoBackupFolder = "_autobackup";
|
|
static const char* kHoldFolder = "$tmp_hold"; // conform to the ignored file types $tmp[0-9]*_ regex
|
|
static const char* kSaveBackupFolder = "_savebackup";
|
|
static const char* kResizeTempFolder = "$tmp_resize"; // conform to the ignored file types $tmp[0-9]*_ regex
|
|
|
|
static const char* kBackupOrTempFolders[] =
|
|
{
|
|
kAutoBackupFolder,
|
|
kHoldFolder,
|
|
kSaveBackupFolder,
|
|
kResizeTempFolder,
|
|
"_hold", // legacy name
|
|
"_tmpresize", // legacy name
|
|
};
|
|
|
|
static const char* kLevelPathForSliceEditing = "EngineAssets/LevelForSliceEditing/LevelForSliceEditing.ly";
|
|
|
|
static bool IsSliceFile(const QString& filePath)
|
|
{
|
|
return filePath.endsWith(AzToolsFramework::SliceUtilities::GetSliceFileExtension().c_str(), Qt::CaseInsensitive);
|
|
}
|
|
|
|
namespace Internal
|
|
{
|
|
bool SaveLevel()
|
|
{
|
|
if (!GetIEditor()->GetDocument()->DoSave(GetIEditor()->GetDocument()->GetActivePathName(), true))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// CCryEditDoc construction/destruction
|
|
|
|
CCryEditDoc::CCryEditDoc()
|
|
: doc_validate_surface_types(nullptr)
|
|
, m_modifiedModuleFlags(eModifiedNothing)
|
|
{
|
|
////////////////////////////////////////////////////////////////////////
|
|
// Set member variables to initial values
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
m_fogTemplate = GetIEditor()->FindTemplate("Fog");
|
|
m_environmentTemplate = GetIEditor()->FindTemplate("Environment");
|
|
|
|
if (m_environmentTemplate)
|
|
{
|
|
m_fogTemplate = m_environmentTemplate->findChild("Fog");
|
|
}
|
|
else
|
|
{
|
|
m_environmentTemplate = XmlHelpers::CreateXmlNode("Environment");
|
|
}
|
|
|
|
GetIEditor()->SetDocument(this);
|
|
CLogFile::WriteLine("Document created");
|
|
RegisterConsoleVariables();
|
|
|
|
MainWindow::instance()->GetActionManager()->RegisterActionHandler(ID_FILE_SAVE_AS, this, &CCryEditDoc::OnFileSaveAs);
|
|
bool isPrefabSystemEnabled = false;
|
|
AzFramework::ApplicationRequests::Bus::BroadcastResult(isPrefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
|
|
if (isPrefabSystemEnabled)
|
|
{
|
|
m_prefabSystemComponentInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabSystemComponentInterface>::Get();
|
|
AZ_Assert(m_prefabSystemComponentInterface, "PrefabSystemComponentInterface is not found.");
|
|
m_prefabEditorEntityOwnershipInterface = AZ::Interface<AzToolsFramework::PrefabEditorEntityOwnershipInterface>::Get();
|
|
AZ_Assert(m_prefabEditorEntityOwnershipInterface, "PrefabEditorEntityOwnershipInterface is not found.");
|
|
m_prefabLoaderInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabLoaderInterface>::Get();
|
|
AZ_Assert(m_prefabLoaderInterface, "PrefabLoaderInterface is not found.");
|
|
m_prefabIntegrationInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabIntegrationInterface>::Get();
|
|
AZ_Assert(m_prefabIntegrationInterface, "PrefabIntegrationInterface is not found.");
|
|
}
|
|
}
|
|
|
|
CCryEditDoc::~CCryEditDoc()
|
|
{
|
|
GetIEditor()->SetDocument(nullptr);
|
|
|
|
CLogFile::WriteLine("Document destroyed");
|
|
|
|
AzToolsFramework::SliceEditorEntityOwnershipServiceNotificationBus::Handler::BusDisconnect();
|
|
}
|
|
|
|
bool CCryEditDoc::IsModified() const
|
|
{
|
|
return m_modified;
|
|
}
|
|
|
|
void CCryEditDoc::SetModifiedFlag(bool modified)
|
|
{
|
|
m_modified = modified;
|
|
}
|
|
|
|
QString CCryEditDoc::GetLevelPathName() const
|
|
{
|
|
return m_pathName;
|
|
}
|
|
|
|
void CCryEditDoc::SetPathName(const QString& pathName)
|
|
{
|
|
if (IsSliceFile(pathName))
|
|
{
|
|
m_pathName = kLevelPathForSliceEditing;
|
|
m_slicePathName = pathName;
|
|
}
|
|
else
|
|
{
|
|
m_pathName = pathName;
|
|
m_slicePathName.clear();
|
|
}
|
|
SetTitle(pathName.isEmpty() ? tr("Untitled") : PathUtil::GetFileName(pathName.toUtf8().data()).c_str());
|
|
}
|
|
|
|
QString CCryEditDoc::GetSlicePathName() const
|
|
{
|
|
return m_slicePathName;
|
|
}
|
|
|
|
CCryEditDoc::DocumentEditingMode CCryEditDoc::GetEditMode() const
|
|
{
|
|
return m_slicePathName.isEmpty() ? CCryEditDoc::DocumentEditingMode::LevelEdit : CCryEditDoc::DocumentEditingMode::SliceEdit;
|
|
}
|
|
|
|
QString CCryEditDoc::GetActivePathName() const
|
|
{
|
|
return GetEditMode() == CCryEditDoc::DocumentEditingMode::SliceEdit ? GetSlicePathName() : GetLevelPathName();
|
|
}
|
|
|
|
QString CCryEditDoc::GetTitle() const
|
|
{
|
|
return m_title;
|
|
}
|
|
|
|
void CCryEditDoc::SetTitle(const QString& title)
|
|
{
|
|
m_title = title;
|
|
}
|
|
|
|
bool CCryEditDoc::IsBackupOrTempLevelSubdirectory(const QString& folderName)
|
|
{
|
|
for (const char* backupOrTempFolderName : kBackupOrTempFolders)
|
|
{
|
|
if (!folderName.compare(backupOrTempFolderName, Qt::CaseInsensitive))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CCryEditDoc::DoSave(const QString& pathName, bool replace)
|
|
{
|
|
if (!OnSaveDocument(pathName.isEmpty() ? GetActivePathName() : pathName))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (replace)
|
|
{
|
|
SetPathName(pathName);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CCryEditDoc::Save()
|
|
{
|
|
return OnSaveDocument(GetActivePathName());
|
|
}
|
|
|
|
void CCryEditDoc::DeleteContents()
|
|
{
|
|
m_hasErrors = false;
|
|
SetDocumentReady(false);
|
|
|
|
GetIEditor()->Notify(eNotify_OnCloseScene);
|
|
CrySystemEventBus::Broadcast(&CrySystemEventBus::Events::OnCryEditorCloseScene);
|
|
|
|
EBUS_EVENT(AzToolsFramework::EditorEntityContextRequestBus, ResetEditorContext);
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Clear all undo info.
|
|
//////////////////////////////////////////////////////////////////////////
|
|
GetIEditor()->FlushUndo();
|
|
|
|
// Notify listeners.
|
|
for (IDocListener* listener : m_listeners)
|
|
{
|
|
listener->OnCloseDocument();
|
|
}
|
|
|
|
GetIEditor()->ResetViews();
|
|
|
|
// Delete all objects from Object Manager.
|
|
GetIEditor()->GetObjectManager()->DeleteAllObjects();
|
|
|
|
// Load scripts data
|
|
SetModifiedFlag(false);
|
|
SetModifiedModules(eModifiedNothing);
|
|
// Clear error reports if open.
|
|
CErrorReportDialog::Clear();
|
|
|
|
// Unload level specific audio binary data.
|
|
LmbrCentral::AudioSystemComponentRequestBus::Broadcast(&LmbrCentral::AudioSystemComponentRequestBus::Events::LevelUnloadAudio);
|
|
|
|
GetIEditor()->Notify(eNotify_OnSceneClosed);
|
|
CrySystemEventBus::Broadcast(&CrySystemEventBus::Events::OnCryEditorSceneClosed);
|
|
}
|
|
|
|
|
|
void CCryEditDoc::Save(CXmlArchive& xmlAr)
|
|
{
|
|
TDocMultiArchive arrXmlAr;
|
|
FillXmlArArray(arrXmlAr, &xmlAr);
|
|
Save(arrXmlAr);
|
|
}
|
|
|
|
void CCryEditDoc::Save(TDocMultiArchive& arrXmlAr)
|
|
{
|
|
bool isPrefabEnabled = false;
|
|
AzFramework::ApplicationRequests::Bus::BroadcastResult(isPrefabEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
|
|
|
|
if (!isPrefabEnabled)
|
|
{
|
|
CAutoDocNotReady autoDocNotReady;
|
|
|
|
if (arrXmlAr[DMAS_GENERAL] != nullptr)
|
|
{
|
|
(*arrXmlAr[DMAS_GENERAL]).root = XmlHelpers::CreateXmlNode("Level");
|
|
(*arrXmlAr[DMAS_GENERAL]).root->setAttr("WaterColor", m_waterColor);
|
|
|
|
char version[50];
|
|
GetIEditor()->GetFileVersion().ToString(version, AZ_ARRAY_SIZE(version));
|
|
(*arrXmlAr[DMAS_GENERAL]).root->setAttr("SandboxVersion", version);
|
|
|
|
SerializeViewSettings((*arrXmlAr[DMAS_GENERAL]));
|
|
|
|
// Fog settings ///////////////////////////////////////////////////////
|
|
SerializeFogSettings((*arrXmlAr[DMAS_GENERAL]));
|
|
}
|
|
}
|
|
AfterSave();
|
|
}
|
|
|
|
|
|
void CCryEditDoc::Load(CXmlArchive& xmlAr, const QString& szFilename)
|
|
{
|
|
TDocMultiArchive arrXmlAr;
|
|
FillXmlArArray(arrXmlAr, &xmlAr);
|
|
CCryEditDoc::Load(arrXmlAr, szFilename);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CCryEditDoc::Load(TDocMultiArchive& arrXmlAr, const QString& szFilename)
|
|
{
|
|
bool isPrefabEnabled = false;
|
|
AzFramework::ApplicationRequests::Bus::BroadcastResult(isPrefabEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
|
|
|
|
m_hasErrors = false;
|
|
|
|
// Register a unique load event
|
|
QString fileName = Path::GetFileName(szFilename);
|
|
QString levelHash;
|
|
if (!isPrefabEnabled)
|
|
{
|
|
levelHash = GetIEditor()->GetSettingsManager()->GenerateContentHash(arrXmlAr[DMAS_GENERAL]->root, fileName);
|
|
}
|
|
else
|
|
{
|
|
levelHash = szFilename;
|
|
}
|
|
SEventLog loadEvent("Level_" + Path::GetFileName(fileName), "", levelHash);
|
|
|
|
// Register this level and its content hash as version
|
|
GetIEditor()->GetSettingsManager()->AddToolVersion(fileName, levelHash);
|
|
GetIEditor()->GetSettingsManager()->RegisterEvent(loadEvent);
|
|
|
|
CAutoDocNotReady autoDocNotReady;
|
|
|
|
HEAP_CHECK
|
|
|
|
CLogFile::FormatLine("Loading from %s...", szFilename.toUtf8().data());
|
|
QString szLevelPath = Path::GetPath(szFilename);
|
|
|
|
{
|
|
// Set game g_levelname variable to the name of current level.
|
|
QString szGameLevelName = Path::GetFileName(szFilename);
|
|
ICVar* sv_map = gEnv->pConsole->GetCVar("sv_map");
|
|
if (sv_map)
|
|
{
|
|
sv_map->Set(szGameLevelName.toUtf8().data());
|
|
}
|
|
}
|
|
|
|
// Starts recording the opening of files using the level category
|
|
if (auto archive = AZ::Interface<AZ::IO::IArchive>::Get(); archive && archive->GetRecordFileOpenList() == AZ::IO::IArchive::RFOM_EngineStartup)
|
|
{
|
|
archive->RecordFileOpen(AZ::IO::IArchive::RFOM_Level);
|
|
}
|
|
|
|
GetIEditor()->Notify(eNotify_OnBeginSceneOpen);
|
|
GetIEditor()->GetMovieSystem()->RemoveAllSequences();
|
|
|
|
{
|
|
// Start recording errors
|
|
const ICVar* pShowErrorDialogOnLoad = gEnv->pConsole->GetCVar("ed_showErrorDialogOnLoad");
|
|
CErrorsRecorder errorsRecorder(pShowErrorDialogOnLoad && (pShowErrorDialogOnLoad->GetIVal() != 0));
|
|
|
|
bool usePrefabSystemForLevels = false;
|
|
AzFramework::ApplicationRequests::Bus::BroadcastResult(
|
|
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemForLevelsEnabled);
|
|
|
|
if (!usePrefabSystemForLevels)
|
|
{
|
|
AZStd::string levelPakPath;
|
|
if (AzFramework::StringFunc::Path::ConstructFull(szLevelPath.toUtf8().data(), "level", "pak", levelPakPath, true))
|
|
{
|
|
// Check whether level.pak is present
|
|
if (!gEnv->pFileIO->Exists(levelPakPath.c_str()))
|
|
{
|
|
CryWarning(
|
|
VALIDATOR_MODULE_EDITOR, VALIDATOR_WARNING,
|
|
"level.pak is missing. This will cause other errors. To fix this, re-export the level.");
|
|
}
|
|
}
|
|
}
|
|
|
|
int t0 = GetTickCount();
|
|
|
|
// Load level-specific audio data.
|
|
AZStd::string levelFileName{ fileName.toUtf8().constData() };
|
|
AZStd::to_lower(levelFileName.begin(), levelFileName.end());
|
|
LmbrCentral::AudioSystemComponentRequestBus::Broadcast(
|
|
&LmbrCentral::AudioSystemComponentRequestBus::Events::LevelLoadAudio, AZStd::string_view{ levelFileName });
|
|
|
|
{
|
|
CAutoLogTime logtime("Game Engine level load");
|
|
GetIEditor()->GetGameEngine()->LoadLevel(true, true);
|
|
}
|
|
|
|
if (!isPrefabEnabled)
|
|
{
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Load water color.
|
|
//////////////////////////////////////////////////////////////////////////
|
|
(*arrXmlAr[DMAS_GENERAL]).root->getAttr("WaterColor", m_waterColor);
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Load View Settings
|
|
//////////////////////////////////////////////////////////////////////////
|
|
SerializeViewSettings((*arrXmlAr[DMAS_GENERAL]));
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Fog settings
|
|
//////////////////////////////////////////////////////////////////////////
|
|
SerializeFogSettings((*arrXmlAr[DMAS_GENERAL]));
|
|
}
|
|
|
|
if (!isPrefabEnabled)
|
|
{
|
|
// Serialize Shader Cache.
|
|
CAutoLogTime logtime("Load Level Shader Cache");
|
|
}
|
|
|
|
{
|
|
// support old version of sequences
|
|
IMovieSystem* pMs = GetIEditor()->GetMovieSystem();
|
|
|
|
if (pMs)
|
|
{
|
|
for (int k = 0; k < pMs->GetNumSequences(); ++k)
|
|
{
|
|
IAnimSequence* seq = pMs->GetSequence(k);
|
|
QString fullname = seq->GetName();
|
|
CBaseObject* pObj = GetIEditor()->GetObjectManager()->FindObject(fullname);
|
|
|
|
if (!pObj)
|
|
{
|
|
pObj = GetIEditor()->GetObjectManager()->NewObject("SequenceObject", nullptr, fullname);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
CAutoLogTime logtime("Post Load");
|
|
|
|
// Notify listeners.
|
|
for (IDocListener* listener : m_listeners)
|
|
{
|
|
listener->OnLoadDocument();
|
|
}
|
|
}
|
|
|
|
CSurfaceTypeValidator().Validate();
|
|
|
|
LogLoadTime(GetTickCount() - t0);
|
|
// Loaded with success, remove event from log file
|
|
GetIEditor()->GetSettingsManager()->UnregisterEvent(loadEvent);
|
|
}
|
|
|
|
GetIEditor()->Notify(eNotify_OnEndSceneOpen);
|
|
}
|
|
|
|
void CCryEditDoc::AfterSave()
|
|
{
|
|
// When saving level also save editor settings
|
|
// Save settings
|
|
gSettings.Save();
|
|
GetIEditor()->GetDisplaySettings()->SaveRegistry();
|
|
MainWindow::instance()->SaveConfig();
|
|
}
|
|
|
|
void CCryEditDoc::SerializeViewSettings(CXmlArchive& xmlAr)
|
|
{
|
|
// Load or restore the viewer settings from an XML
|
|
if (xmlAr.bLoading)
|
|
{
|
|
bool useOldViewFormat = false;
|
|
// Loading
|
|
CLogFile::WriteLine("Loading View settings...");
|
|
|
|
int numberOfGameViewports = GetIEditor()->GetViewManager()->GetNumberOfGameViewports();
|
|
|
|
for (int i = 0; i < numberOfGameViewports; i++)
|
|
{
|
|
XmlNodeRef view;
|
|
Vec3 vp(0.0f, 0.0f, 256.0f);
|
|
Ang3 va(ZERO);
|
|
|
|
auto viewName = QString("View%1").arg(i);
|
|
view = xmlAr.root->findChild(viewName.toUtf8().constData());
|
|
|
|
if (!view)
|
|
{
|
|
view = xmlAr.root->findChild("View");
|
|
if (view)
|
|
{
|
|
useOldViewFormat = true;
|
|
}
|
|
}
|
|
|
|
if (view)
|
|
{
|
|
auto viewerPosName = QString("ViewerPos%1").arg(useOldViewFormat ? "" : QString::number(i));
|
|
view->getAttr(viewerPosName.toUtf8().constData(), vp);
|
|
auto viewerAnglesName = QString("ViewerAngles%1").arg(useOldViewFormat ? "" : QString::number(i));
|
|
view->getAttr(viewerAnglesName.toUtf8().constData(), va);
|
|
}
|
|
|
|
Matrix34 tm = Matrix34::CreateRotationXYZ(va);
|
|
tm.SetTranslation(vp);
|
|
|
|
auto viewportContextManager = AZ::Interface<AZ::RPI::ViewportContextRequestsInterface>::Get();
|
|
if (auto viewportContext = viewportContextManager->GetViewportContextById(i))
|
|
{
|
|
viewportContext->SetCameraTransform(LYTransformToAZTransform(tm));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Storing
|
|
CLogFile::WriteLine("Storing View settings...");
|
|
|
|
int numberOfGameViewports = GetIEditor()->GetViewManager()->GetNumberOfGameViewports();
|
|
|
|
for (int i = 0; i < numberOfGameViewports; i++)
|
|
{
|
|
auto viewName = QString("View%1").arg(i);
|
|
XmlNodeRef view = xmlAr.root->newChild(viewName.toUtf8().constData());
|
|
|
|
CViewport* pVP = GetIEditor()->GetViewManager()->GetView(i);
|
|
|
|
if (pVP)
|
|
{
|
|
Vec3 pos = pVP->GetViewTM().GetTranslation();
|
|
Ang3 angles = Ang3::GetAnglesXYZ(Matrix33(pVP->GetViewTM()));
|
|
auto viewerPosName = QString("ViewerPos%1").arg(i);
|
|
view->setAttr(viewerPosName.toUtf8().constData(), pos);
|
|
auto viewerAnglesName = QString("ViewerAngles%1").arg(i);
|
|
view->setAttr(viewerAnglesName.toUtf8().constData(), angles);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CCryEditDoc::SerializeFogSettings(CXmlArchive& xmlAr)
|
|
{
|
|
if (xmlAr.bLoading)
|
|
{
|
|
CLogFile::WriteLine("Loading Fog settings...");
|
|
|
|
XmlNodeRef fog = xmlAr.root->findChild("Fog");
|
|
|
|
if (!fog)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (m_fogTemplate)
|
|
{
|
|
CXmlTemplate::GetValues(m_fogTemplate, fog);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CLogFile::WriteLine("Storing Fog settings...");
|
|
|
|
XmlNodeRef fog = xmlAr.root->newChild("Fog");
|
|
|
|
if (m_fogTemplate)
|
|
{
|
|
CXmlTemplate::SetValues(m_fogTemplate, fog);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CCryEditDoc::SetModifiedModules(EModifiedModule eModifiedModule, bool boSet)
|
|
{
|
|
if (!boSet)
|
|
{
|
|
m_modifiedModuleFlags &= ~eModifiedModule;
|
|
}
|
|
else
|
|
{
|
|
if (eModifiedModule == eModifiedNothing)
|
|
{
|
|
m_modifiedModuleFlags = eModifiedNothing;
|
|
}
|
|
else
|
|
{
|
|
m_modifiedModuleFlags |= eModifiedModule;
|
|
}
|
|
}
|
|
}
|
|
|
|
int CCryEditDoc::GetModifiedModule()
|
|
{
|
|
return m_modifiedModuleFlags;
|
|
}
|
|
|
|
bool CCryEditDoc::CanCloseFrame()
|
|
{
|
|
// Ask the base class to ask for saving, which also includes the save
|
|
// status of the plugins. Additionaly we query if all the plugins can exit
|
|
// now. A reason for a failure might be that one of the plugins isn't
|
|
// currently processing data or has other unsaved information which
|
|
// are not serialized in the project file
|
|
if (!SaveModified())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!GetIEditor()->GetPluginManager()->CanAllPluginsExitNow())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// If there is an export in process, exiting will corrupt it
|
|
if (CGameExporter::GetCurrentExporter() != nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CCryEditDoc::SaveModified()
|
|
{
|
|
if (!IsModified())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool usePrefabSystemForLevels = false;
|
|
AzFramework::ApplicationRequests::Bus::BroadcastResult(
|
|
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemForLevelsEnabled);
|
|
if (!usePrefabSystemForLevels)
|
|
{
|
|
QMessageBox saveModifiedMessageBox(AzToolsFramework::GetActiveWindow());
|
|
saveModifiedMessageBox.setText(QString("Save changes to %1?").arg(GetTitle()));
|
|
saveModifiedMessageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
|
|
saveModifiedMessageBox.setIcon(QMessageBox::Icon::Question);
|
|
|
|
auto button = QMessageBox::question(
|
|
AzToolsFramework::GetActiveWindow(), QString(), tr("Save changes to %1?").arg(GetTitle()),
|
|
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
|
|
switch (button)
|
|
{
|
|
case QMessageBox::Cancel:
|
|
return false;
|
|
case QMessageBox::Yes:
|
|
return DoFileSave();
|
|
case QMessageBox::No:
|
|
SetModifiedFlag(false);
|
|
return true;
|
|
}
|
|
Q_UNREACHABLE();
|
|
}
|
|
else
|
|
{
|
|
AzToolsFramework::Prefab::TemplateId rootPrefabTemplateId = m_prefabEditorEntityOwnershipInterface->GetRootPrefabTemplateId();
|
|
if (!m_prefabSystemComponentInterface->AreDirtyTemplatesPresent(rootPrefabTemplateId))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
int prefabSaveSelection = m_prefabIntegrationInterface->ExecuteClosePrefabDialog(rootPrefabTemplateId);
|
|
|
|
// In order to get the accept and reject codes of QDialog and QDialogButtonBox aligned, we do (1-prefabSaveSelection) here.
|
|
// For example, QDialog::Rejected(0) is emitted when dialog is closed. But the int value corresponds to
|
|
// QDialogButtonBox::AcceptRole(0).
|
|
switch (1 - prefabSaveSelection)
|
|
{
|
|
case QDialogButtonBox::AcceptRole:
|
|
return true;
|
|
case QDialogButtonBox::RejectRole:
|
|
return false;
|
|
case QDialogButtonBox::InvalidRole:
|
|
SetModifiedFlag(false);
|
|
return true;
|
|
}
|
|
Q_UNREACHABLE();
|
|
}
|
|
}
|
|
|
|
void CCryEditDoc::OnFileSaveAs()
|
|
{
|
|
CLevelFileDialog levelFileDialog(false);
|
|
levelFileDialog.show();
|
|
levelFileDialog.adjustSize();
|
|
|
|
if (levelFileDialog.exec() == QDialog::Accepted)
|
|
{
|
|
if (OnSaveDocument(levelFileDialog.GetFileName()))
|
|
{
|
|
CCryEditApp::instance()->AddToRecentFileList(levelFileDialog.GetFileName());
|
|
bool usePrefabSystemForLevels = false;
|
|
AzFramework::ApplicationRequests::Bus::BroadcastResult(
|
|
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemForLevelsEnabled);
|
|
if (usePrefabSystemForLevels)
|
|
{
|
|
AzToolsFramework::Prefab::TemplateId rootPrefabTemplateId =
|
|
m_prefabEditorEntityOwnershipInterface->GetRootPrefabTemplateId();
|
|
SetModifiedFlag(m_prefabSystemComponentInterface->AreDirtyTemplatesPresent(rootPrefabTemplateId));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CCryEditDoc::OnOpenDocument(const QString& lpszPathName)
|
|
{
|
|
TOpenDocContext context;
|
|
if (!BeforeOpenDocument(lpszPathName, context))
|
|
{
|
|
return false;
|
|
}
|
|
return DoOpenDocument(context);
|
|
}
|
|
|
|
bool CCryEditDoc::BeforeOpenDocument(const QString& lpszPathName, TOpenDocContext& context)
|
|
{
|
|
const AZ::TimeMs timeMs = AZ::GetRealElapsedTimeMs();
|
|
const double timeSec = AZ::TimeMsToSecondsDouble(timeMs);
|
|
const CTimeValue loading_start_time(timeSec);
|
|
|
|
bool usePrefabSystemForLevels = false;
|
|
AzFramework::ApplicationRequests::Bus::BroadcastResult(
|
|
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemForLevelsEnabled);
|
|
|
|
if (!usePrefabSystemForLevels)
|
|
{
|
|
// ensure we close any open packs
|
|
if (!GetIEditor()->GetLevelFolder().isEmpty())
|
|
{
|
|
GetIEditor()->GetSystem()->GetIPak()->ClosePack((GetIEditor()->GetLevelFolder() + "\\level.pak").toUtf8().data());
|
|
}
|
|
}
|
|
|
|
// restore directory to root.
|
|
QDir::setCurrent(GetIEditor()->GetPrimaryCDFolder());
|
|
|
|
QString absolutePath = lpszPathName;
|
|
QFileInfo fileInfo(absolutePath);
|
|
QString friendlyDisplayName = Path::GetRelativePath(absolutePath, true);
|
|
CLogFile::FormatLine("Opening level %s", friendlyDisplayName.toUtf8().data());
|
|
|
|
// normalize the file path.
|
|
absolutePath = Path::ToUnixPath(QFileInfo(absolutePath).canonicalFilePath());
|
|
context.loading_start_time = loading_start_time;
|
|
if (IsSliceFile(absolutePath))
|
|
{
|
|
context.absoluteLevelPath = Path::GamePathToFullPath(kLevelPathForSliceEditing);
|
|
context.absoluteSlicePath = absolutePath;
|
|
}
|
|
else
|
|
{
|
|
context.absoluteLevelPath = absolutePath;
|
|
context.absoluteSlicePath = "";
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool CCryEditDoc::DoOpenDocument(TOpenDocContext& context)
|
|
{
|
|
const CTimeValue& loading_start_time = context.loading_start_time;
|
|
|
|
bool isPrefabEnabled = false;
|
|
AzFramework::ApplicationRequests::Bus::BroadcastResult(isPrefabEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
|
|
|
|
// normalize the path so that its the same in all following calls:
|
|
QString levelFilePath = QFileInfo(context.absoluteLevelPath).absoluteFilePath();
|
|
context.absoluteLevelPath = levelFilePath;
|
|
|
|
m_bLoadFailed = false;
|
|
|
|
auto pIPak = GetIEditor()->GetSystem()->GetIPak();
|
|
|
|
QString levelFolderAbsolutePath = QFileInfo(context.absoluteLevelPath).absolutePath();
|
|
|
|
|
|
if (!isPrefabEnabled)
|
|
{
|
|
// if the level pack exists, open that, too:
|
|
QString levelPackFileAbsolutePath = QDir(levelFolderAbsolutePath).absoluteFilePath("level.pak");
|
|
|
|
// we mount the pack (level.pak) using the folder its sitting in as the mountpoint (first parameter)
|
|
pIPak->OpenPack(levelFolderAbsolutePath.toUtf8().constData(), levelPackFileAbsolutePath.toUtf8().constData());
|
|
}
|
|
|
|
TDocMultiArchive arrXmlAr = {};
|
|
|
|
if (!isPrefabEnabled)
|
|
{
|
|
if (!LoadXmlArchiveArray(arrXmlAr, levelFilePath, levelFolderAbsolutePath))
|
|
{
|
|
m_bLoadFailed = true;
|
|
return false;
|
|
}
|
|
}
|
|
if (!LoadLevel(arrXmlAr, context.absoluteLevelPath))
|
|
{
|
|
m_bLoadFailed = true;
|
|
}
|
|
|
|
ReleaseXmlArchiveArray(arrXmlAr);
|
|
|
|
if (m_bLoadFailed)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Load AZ entities for the editor.
|
|
if (context.absoluteSlicePath.isEmpty())
|
|
{
|
|
if (!LoadEntitiesFromLevel(context.absoluteLevelPath))
|
|
{
|
|
m_bLoadFailed = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!LoadEntitiesFromSlice(context.absoluteSlicePath))
|
|
{
|
|
m_bLoadFailed = true;
|
|
}
|
|
}
|
|
|
|
if (m_bLoadFailed)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
StartStreamingLoad();
|
|
|
|
const AZ::TimeMs timeMs = AZ::GetRealElapsedTimeMs();
|
|
const double timeSec = AZ::TimeMsToSecondsDouble(timeMs);
|
|
const CTimeValue loading_end_time(timeSec);
|
|
|
|
CLogFile::FormatLine("-----------------------------------------------------------");
|
|
CLogFile::FormatLine("Successfully opened document %s", context.absoluteLevelPath.toUtf8().data());
|
|
CLogFile::FormatLine("Level loading time: %.2f seconds", (loading_end_time - loading_start_time).GetSeconds());
|
|
CLogFile::FormatLine("-----------------------------------------------------------");
|
|
|
|
// It assumes loaded levels have already been exported. Can be a big fat lie, though.
|
|
// The right way would require us to save to the level folder the export status of the
|
|
// level.
|
|
SetLevelExported(true);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CCryEditDoc::OnNewDocument()
|
|
{
|
|
DeleteContents();
|
|
m_pathName.clear();
|
|
m_slicePathName.clear();
|
|
SetModifiedFlag(false);
|
|
return true;
|
|
}
|
|
|
|
bool CCryEditDoc::OnSaveDocument(const QString& lpszPathName)
|
|
{
|
|
bool saveSuccess = false;
|
|
bool shouldSaveLevel = true;
|
|
if (gEnv->IsEditorSimulationMode())
|
|
{
|
|
// Don't allow saving in AI/Physics mode.
|
|
// Prompt the user to exit Simulation Mode (aka AI/Phyics mode) before saving.
|
|
QWidget* mainWindow = nullptr;
|
|
EBUS_EVENT_RESULT(mainWindow, AzToolsFramework::EditorRequests::Bus, GetMainWindow);
|
|
|
|
QMessageBox msgBox(mainWindow);
|
|
msgBox.setText(tr("You must exit AI/Physics mode before saving."));
|
|
msgBox.setInformativeText(tr("The level will not be saved."));
|
|
msgBox.setIcon(QMessageBox::Warning);
|
|
msgBox.exec();
|
|
}
|
|
else
|
|
{
|
|
if (m_hasErrors || m_bLoadFailed)
|
|
{
|
|
QWidget* mainWindow = nullptr;
|
|
AzToolsFramework::EditorRequests::Bus::BroadcastResult(
|
|
mainWindow,
|
|
&AzToolsFramework::EditorRequests::Bus::Events::GetMainWindow);
|
|
|
|
// Prompt the user that saving may result in data loss. Most of the time this is not desired
|
|
// (which is why 'cancel' is the default interaction), but this does provide users a way to still
|
|
// save their level if this is the only way they can solve the erroneous data.
|
|
QMessageBox msgBox(mainWindow);
|
|
msgBox.setText(tr("Your level loaded with errors, you may lose work if you save."));
|
|
msgBox.setInformativeText(tr("Do you want to save your changes?"));
|
|
msgBox.setIcon(QMessageBox::Warning);
|
|
msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Cancel);
|
|
msgBox.setDefaultButton(QMessageBox::Cancel);
|
|
int result = msgBox.exec();
|
|
switch (result)
|
|
{
|
|
case QMessageBox::Save:
|
|
// The user wishes to save, so don't bail.
|
|
break;
|
|
case QMessageBox::Cancel:
|
|
// The user is canceling the save operation, so stop any saving from occuring.
|
|
shouldSaveLevel = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
TSaveDocContext context;
|
|
if (shouldSaveLevel && BeforeSaveDocument(lpszPathName, context))
|
|
{
|
|
DoSaveDocument(lpszPathName, context);
|
|
saveSuccess = AfterSaveDocument(lpszPathName, context);
|
|
}
|
|
}
|
|
|
|
return saveSuccess;
|
|
}
|
|
|
|
bool CCryEditDoc::BeforeSaveDocument(const QString& lpszPathName, TSaveDocContext& context)
|
|
{
|
|
// Don't save level data if any conflict exists
|
|
if (HasLayerNameConflicts())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Restore directory to root.
|
|
QDir::setCurrent(GetIEditor()->GetPrimaryCDFolder());
|
|
|
|
// If we do not have a level loaded, we will also have an empty path, and that will
|
|
// cause problems later in the save process. Early out here if that's the case
|
|
QString levelFriendlyName = QFileInfo(lpszPathName).fileName();
|
|
if (levelFriendlyName.isEmpty())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
CryLog("Saving to %s...", levelFriendlyName.toUtf8().data());
|
|
GetIEditor()->Notify(eNotify_OnBeginSceneSave);
|
|
|
|
bool bSaved(true);
|
|
|
|
context.bSaved = bSaved;
|
|
return true;
|
|
}
|
|
|
|
bool CCryEditDoc::HasLayerNameConflicts() const
|
|
{
|
|
AZStd::vector<AZ::Entity*> editorEntities;
|
|
AzToolsFramework::EditorEntityContextRequestBus::Broadcast(
|
|
&AzToolsFramework::EditorEntityContextRequestBus::Events::GetLooseEditorEntities,
|
|
editorEntities);
|
|
|
|
AZStd::unordered_map<AZStd::string, int> nameConflictMapping;
|
|
for (AZ::Entity* entity : editorEntities)
|
|
{
|
|
AzToolsFramework::Layers::EditorLayerComponentRequestBus::Event(
|
|
entity->GetId(),
|
|
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::UpdateLayerNameConflictMapping,
|
|
nameConflictMapping);
|
|
}
|
|
|
|
if (!nameConflictMapping.empty())
|
|
{
|
|
AzToolsFramework::Layers::NameConflictWarning* nameConflictWarning = new AzToolsFramework::Layers::NameConflictWarning(
|
|
MainWindow::instance(),
|
|
nameConflictMapping);
|
|
nameConflictWarning->exec();
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CCryEditDoc::DoSaveDocument(const QString& filename, TSaveDocContext& context)
|
|
{
|
|
bool& bSaved = context.bSaved;
|
|
if (!bSaved)
|
|
{
|
|
return false;
|
|
}
|
|
// Paranoia - we shouldn't get this far into the save routine without a level loaded (empty levelPath)
|
|
// If nothing is loaded, we don't need to save anything
|
|
if (filename.isEmpty())
|
|
{
|
|
bSaved = false;
|
|
return false;
|
|
}
|
|
|
|
// Save Tag Point locations to file if auto save of tag points disabled
|
|
if (!gSettings.bAutoSaveTagPoints)
|
|
{
|
|
CCryEditApp::instance()->SaveTagLocations();
|
|
}
|
|
|
|
QString normalizedPath = Path::ToUnixPath(filename);
|
|
if (IsSliceFile(normalizedPath))
|
|
{
|
|
bSaved = SaveSlice(normalizedPath);
|
|
}
|
|
else
|
|
{
|
|
bSaved = SaveLevel(normalizedPath);
|
|
}
|
|
|
|
// Changes filename for this document.
|
|
SetPathName(normalizedPath);
|
|
return bSaved;
|
|
}
|
|
|
|
bool CCryEditDoc::AfterSaveDocument([[maybe_unused]] const QString& lpszPathName, TSaveDocContext& context, bool bShowPrompt)
|
|
{
|
|
bool bSaved = context.bSaved;
|
|
|
|
GetIEditor()->Notify(eNotify_OnEndSceneSave);
|
|
|
|
if (!bSaved)
|
|
{
|
|
if (bShowPrompt)
|
|
{
|
|
QMessageBox::warning(QApplication::activeWindow(), QString(), QObject::tr("Save Failed"), QMessageBox::Ok);
|
|
}
|
|
CLogFile::WriteLine("$4Document saving has failed.");
|
|
}
|
|
else
|
|
{
|
|
CLogFile::WriteLine("$3Document successfully saved");
|
|
SetModifiedFlag(false);
|
|
SetModifiedModules(eModifiedNothing);
|
|
MainWindow::instance()->ResetAutoSaveTimers();
|
|
}
|
|
|
|
return bSaved;
|
|
}
|
|
|
|
static bool TryRenameFile(const QString& oldPath, const QString& newPath, int retryAttempts=10)
|
|
{
|
|
QFile(newPath).setPermissions(QFile::ReadOther | QFile::WriteOther);
|
|
QFile::remove(newPath);
|
|
|
|
// try a few times, something can lock the file (such as virus scanner, etc).
|
|
for (int attempts = 0; attempts < retryAttempts; ++attempts)
|
|
{
|
|
if (QFile::rename(oldPath, newPath))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(100));
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CCryEditDoc::SaveLevel(const QString& filename)
|
|
{
|
|
AZ_PROFILE_FUNCTION(Editor);
|
|
QWaitCursor wait;
|
|
|
|
CAutoCheckOutDialogEnableForAll enableForAll;
|
|
|
|
QString fullPathName = Path::ToUnixPath(filename);
|
|
QString originaLevelFilename = Path::GetFile(m_pathName);
|
|
if (QFileInfo(filename).isRelative())
|
|
{
|
|
// Resolving the path through resolvepath would normalize and lowcase it, and in this case, we don't want that.
|
|
fullPathName = Path::ToUnixPath(QDir(QString::fromUtf8(gEnv->pFileIO->GetAlias("@projectroot@"))).absoluteFilePath(fullPathName));
|
|
}
|
|
|
|
if (!CFileUtil::OverwriteFile(fullPathName))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
{
|
|
|
|
AZ_PROFILE_SCOPE(Editor, "CCryEditDoc::SaveLevel BackupBeforeSave");
|
|
BackupBeforeSave();
|
|
}
|
|
|
|
// need to copy existing level data before saving to different folder
|
|
const QString oldLevelFolder = Path::GetPath(GetLevelPathName()); // get just the folder name
|
|
QString newLevelFolder = Path::GetPath(fullPathName);
|
|
|
|
CFileUtil::CreateDirectory(newLevelFolder.toUtf8().data());
|
|
GetIEditor()->GetGameEngine()->SetLevelPath(newLevelFolder);
|
|
|
|
// QFileInfo operator== takes care of many side cases and will return true
|
|
// if the folder is the same folder, even if other things (like slashes, etc) are wrong
|
|
if (QFileInfo(oldLevelFolder) != QFileInfo(newLevelFolder))
|
|
{
|
|
// if we're saving to a new folder, we need to copy the old folder tree.
|
|
auto pIPak = GetIEditor()->GetSystem()->GetIPak();
|
|
|
|
const QString oldLevelPattern = QDir(oldLevelFolder).absoluteFilePath("*.*");
|
|
const QString oldLevelName = Path::GetFile(GetLevelPathName());
|
|
const QString oldLevelXml = Path::ReplaceExtension(oldLevelName, "xml");
|
|
AZ::IO::ArchiveFileIterator findHandle = pIPak->FindFirst(oldLevelPattern.toUtf8().data(), AZ::IO::FileSearchLocation::Any);
|
|
if (findHandle)
|
|
{
|
|
do
|
|
{
|
|
const QString sourceName{ QString::fromUtf8(findHandle.m_filename.data(), aznumeric_cast<int>(findHandle.m_filename.size())) };
|
|
if ((findHandle.m_fileDesc.nAttrib & AZ::IO::FileDesc::Attribute::Subdirectory) == AZ::IO::FileDesc::Attribute::Subdirectory)
|
|
{
|
|
// we only end up here if sourceName is a folder name.
|
|
bool skipDir = sourceName == "." || sourceName == "..";
|
|
skipDir |= IsBackupOrTempLevelSubdirectory(sourceName);
|
|
skipDir |= sourceName == "Layers"; // layers folder will be created and written out as part of saving
|
|
if (!skipDir)
|
|
{
|
|
QString oldFolderName = QDir(oldLevelFolder).absoluteFilePath(sourceName);
|
|
QString newFolderName = QDir(newLevelFolder).absoluteFilePath(sourceName);
|
|
|
|
CFileUtil::CreateDirectory(newFolderName.toUtf8().data());
|
|
CFileUtil::CopyTree(oldFolderName, newFolderName);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
bool skipFile = sourceName.endsWith(".cry", Qt::CaseInsensitive) ||
|
|
sourceName.endsWith(".ly", Qt::CaseInsensitive) ||
|
|
sourceName == originaLevelFilename; // level file will be written out by saving, ignore the source one
|
|
if (skipFile)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// close any paks in the source folder so that when the paks are re-opened there is
|
|
// no stale cached metadata in the pak system
|
|
if (sourceName.endsWith(".pak", Qt::CaseInsensitive))
|
|
{
|
|
QString oldPackName = QDir(oldLevelFolder).absoluteFilePath(sourceName);
|
|
pIPak->ClosePack(oldPackName.toUtf8().constData());
|
|
}
|
|
|
|
QString destName = sourceName;
|
|
// copy oldLevel.xml -> newLevel.xml
|
|
if (sourceName.compare(oldLevelXml, Qt::CaseInsensitive) == 0)
|
|
{
|
|
destName = Path::ReplaceExtension(Path::GetFile(fullPathName), "xml");
|
|
}
|
|
|
|
QString oldFilePath = QDir(oldLevelFolder).absoluteFilePath(sourceName);
|
|
QString newFilePath = QDir(newLevelFolder).absoluteFilePath(destName);
|
|
CFileUtil::CopyFile(oldFilePath, newFilePath);
|
|
} while ((findHandle = pIPak->FindNext(findHandle)));
|
|
pIPak->FindClose(findHandle);
|
|
}
|
|
|
|
// ensure that copied files are not read-only
|
|
CFileUtil::ForEach(newLevelFolder, [](const QString& filePath)
|
|
{
|
|
QFile(filePath).setPermissions(QFile::ReadOther | QFile::WriteOther);
|
|
});
|
|
|
|
}
|
|
|
|
// Save level to XML archive.
|
|
CXmlArchive xmlAr;
|
|
Save(xmlAr);
|
|
|
|
// temp files (to be ignored by AssetProcessor take the form $tmp[0-9]*_...). we will conform
|
|
// to that to make this file invisible to AP until it has been written completely.
|
|
QString tempSaveFile = QDir(newLevelFolder).absoluteFilePath("$tmp_levelSave.tmp");
|
|
QFile(tempSaveFile).setPermissions(QFile::ReadOther | QFile::WriteOther);
|
|
QFile::remove(tempSaveFile);
|
|
|
|
// Save AZ entities to the editor level.
|
|
|
|
bool contentsAllSaved = false; // abort level save if anything within it fails
|
|
|
|
auto tempFilenameStrData = tempSaveFile.toStdString();
|
|
auto filenameStrData = fullPathName.toStdString();
|
|
|
|
bool isPrefabEnabled = false;
|
|
AzFramework::ApplicationRequests::Bus::BroadcastResult(isPrefabEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
|
|
|
|
if (!isPrefabEnabled)
|
|
{
|
|
AZStd::vector<char> entitySaveBuffer;
|
|
bool savedEntities = false;
|
|
CPakFile pakFile;
|
|
|
|
{
|
|
AZ_PROFILE_SCOPE(Editor, "CCryEditDoc::SaveLevel Open PakFile");
|
|
if (!pakFile.Open(tempSaveFile.toUtf8().data(), false))
|
|
{
|
|
gEnv->pLog->LogWarning("Unable to open pack file %s for writing", tempSaveFile.toUtf8().data());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
AZStd::vector<AZ::Entity*> editorEntities;
|
|
AzToolsFramework::EditorEntityContextRequestBus::Broadcast(
|
|
&AzToolsFramework::EditorEntityContextRequestBus::Events::GetLooseEditorEntities,
|
|
editorEntities);
|
|
|
|
AZStd::vector<AZ::Entity*> layerEntities;
|
|
AZ::SliceComponent::SliceReferenceToInstancePtrs instancesInLayers;
|
|
for (AZ::Entity* entity : editorEntities)
|
|
{
|
|
AzToolsFramework::Layers::LayerResult layerSaveResult(AzToolsFramework::Layers::LayerResult::CreateSuccess());
|
|
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
|
|
layerSaveResult,
|
|
entity->GetId(),
|
|
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::WriteLayerAndGetEntities,
|
|
newLevelFolder,
|
|
layerEntities,
|
|
instancesInLayers);
|
|
layerSaveResult.MessageResult();
|
|
}
|
|
|
|
AZ::IO::ByteContainerStream<AZStd::vector<char>> entitySaveStream(&entitySaveBuffer);
|
|
{
|
|
AZ_PROFILE_SCOPE(Editor, "CCryEditDoc::SaveLevel Save Entities To Stream");
|
|
EBUS_EVENT_RESULT(
|
|
savedEntities, AzToolsFramework::EditorEntityContextRequestBus, SaveToStreamForEditor, entitySaveStream, layerEntities,
|
|
instancesInLayers);
|
|
}
|
|
|
|
for (AZ::Entity* entity : editorEntities)
|
|
{
|
|
AzToolsFramework::Layers::EditorLayerComponentRequestBus::Event(
|
|
entity->GetId(), &AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::RestoreEditorData);
|
|
}
|
|
|
|
if (savedEntities)
|
|
{
|
|
AZ_PROFILE_SCOPE(AzToolsFramework, "CCryEditDoc::SaveLevel Updated PakFile levelEntities.editor_xml");
|
|
pakFile.UpdateFile("levelentities.editor_xml", entitySaveBuffer.begin(), static_cast<int>(entitySaveBuffer.size()));
|
|
|
|
// Save XML archive to pak file.
|
|
bool bSaved = xmlAr.SaveToPak(Path::GetPath(tempSaveFile), pakFile);
|
|
if (bSaved)
|
|
{
|
|
contentsAllSaved = true;
|
|
}
|
|
else
|
|
{
|
|
gEnv->pLog->LogWarning("Unable to write the level data to file %s", tempSaveFile.toUtf8().data());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gEnv->pLog->LogWarning("Unable to generate entity data for level save %s", tempSaveFile.toUtf8().data());
|
|
}
|
|
|
|
pakFile.Close();
|
|
}
|
|
else
|
|
{
|
|
if (m_prefabEditorEntityOwnershipInterface)
|
|
{
|
|
AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
|
|
AZ_Assert(fileIO, "No File IO implementation available");
|
|
|
|
AZ::IO::HandleType tempSaveFileHandle;
|
|
AZ::IO::Result openResult = fileIO->Open(tempFilenameStrData.c_str(), AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeBinary, tempSaveFileHandle);
|
|
contentsAllSaved = openResult;
|
|
if (openResult)
|
|
{
|
|
AZ::IO::FileIOStream stream(tempSaveFileHandle, AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeBinary, false);
|
|
contentsAllSaved = m_prefabEditorEntityOwnershipInterface->SaveToStream(stream, AZStd::string_view(filenameStrData.data(), filenameStrData.size()));
|
|
stream.Close();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!contentsAllSaved)
|
|
{
|
|
AZ_Error("Editor", false, "Error when writing level '%s' into tmpfile '%s'", filenameStrData.c_str(), tempFilenameStrData.c_str());
|
|
QFile::remove(tempSaveFile);
|
|
return false;
|
|
}
|
|
|
|
if (!TryRenameFile(tempSaveFile, fullPathName))
|
|
{
|
|
gEnv->pLog->LogWarning("Unable to move file %s to %s when saving", tempSaveFile.toUtf8().data(), fullPathName.toUtf8().data());
|
|
return false;
|
|
}
|
|
|
|
// Commit changes to the disk.
|
|
_flushall();
|
|
|
|
AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast(&AzToolsFramework::ToolsApplicationEvents::OnSaveLevel);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CCryEditDoc::SaveSlice(const QString& filename)
|
|
{
|
|
using namespace AzToolsFramework::SliceUtilities;
|
|
|
|
// Gather entities from live slice in memory
|
|
AZ::SliceComponent* liveSlice = nullptr;
|
|
AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::BroadcastResult(liveSlice,
|
|
&AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Events::GetEditorRootSlice);
|
|
if (!liveSlice)
|
|
{
|
|
gEnv->pLog->LogWarning("Slice data not found.");
|
|
return false;
|
|
}
|
|
|
|
AZStd::unordered_set<AZ::EntityId> liveEntityIds;
|
|
if (!liveSlice->GetEntityIds(liveEntityIds))
|
|
{
|
|
gEnv->pLog->LogWarning("Error getting entities from slice.");
|
|
return false;
|
|
}
|
|
|
|
|
|
// Prevent save when there are multiple root entities.
|
|
bool foundRootEntity = false;
|
|
for (AZ::EntityId entityId : liveEntityIds)
|
|
{
|
|
AZ::EntityId parentId;
|
|
AZ::TransformBus::EventResult(parentId, entityId, &AZ::TransformBus::Events::GetParentId);
|
|
if (!parentId.IsValid())
|
|
{
|
|
if (foundRootEntity)
|
|
{
|
|
gEnv->pLog->LogWarning("Cannot save a slice with multiple root entities.");
|
|
return false;
|
|
}
|
|
|
|
foundRootEntity = true;
|
|
}
|
|
}
|
|
|
|
// Find target slice asset, and check if it's the same asset we opened
|
|
AZ::Data::AssetId targetAssetId;
|
|
AZ::Data::AssetCatalogRequestBus::BroadcastResult(targetAssetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, filename.toUtf8().data(), azrtti_typeid<AZ::SliceAsset>(), false);
|
|
|
|
QString openedFilepath = Path::ToUnixPath(Path::GetRelativePath(m_slicePathName, true));
|
|
AZ::Data::AssetId openedAssetId;
|
|
AZ::Data::AssetCatalogRequestBus::BroadcastResult(openedAssetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, openedFilepath.toUtf8().data(), azrtti_typeid<AZ::SliceAsset>(), false);
|
|
|
|
if (!targetAssetId.IsValid() || openedAssetId != targetAssetId)
|
|
{
|
|
gEnv->pLog->LogWarning("Slice editor can only modify existing slices. 'New Slice' and 'Save As' are not currently supported.");
|
|
return false;
|
|
}
|
|
|
|
AZ::Data::Asset<AZ::SliceAsset> sliceAssetRef = AZ::Data::AssetManager::Instance().GetAsset<AZ::SliceAsset>(targetAssetId, AZ::Data::AssetLoadBehavior::Default);
|
|
|
|
sliceAssetRef.BlockUntilLoadComplete();
|
|
|
|
if (!sliceAssetRef)
|
|
{
|
|
gEnv->pLog->LogWarning("Error loading slice: %s", filename.toUtf8().data());
|
|
return false;
|
|
}
|
|
|
|
// Get entities from target slice asset.
|
|
AZ::SliceComponent* assetSlice = sliceAssetRef.Get()->GetComponent();
|
|
if (!assetSlice)
|
|
{
|
|
gEnv->pLog->LogWarning("Error reading slice: %s", filename.toUtf8().data());
|
|
return false;
|
|
}
|
|
|
|
AZStd::unordered_set<AZ::EntityId> assetEntityIds;
|
|
if (!assetSlice->GetEntityIds(assetEntityIds))
|
|
{
|
|
gEnv->pLog->LogWarning("Error getting entities from slice: %s", filename.toUtf8().data());
|
|
return false;
|
|
}
|
|
|
|
AZStd::unordered_set<AZ::EntityId> entityAdds;
|
|
AZStd::unordered_set<AZ::EntityId> entityUpdates;
|
|
AZStd::unordered_set<AZ::EntityId> entityRemovals = assetEntityIds;
|
|
|
|
for (AZ::EntityId liveEntityId : liveEntityIds)
|
|
{
|
|
entityRemovals.erase(liveEntityId);
|
|
if (assetEntityIds.find(liveEntityId) != assetEntityIds.end())
|
|
{
|
|
entityUpdates.insert(liveEntityId);
|
|
}
|
|
else
|
|
{
|
|
entityAdds.insert(liveEntityId);
|
|
}
|
|
}
|
|
|
|
// Make a transaction targeting the specified slice
|
|
SliceTransaction::TransactionPtr transaction = SliceTransaction::BeginSlicePush(sliceAssetRef);
|
|
if (!transaction)
|
|
{
|
|
gEnv->pLog->LogWarning("Unable to update slice: %s", filename.toUtf8().data());
|
|
return false;
|
|
}
|
|
|
|
// Tell the transaction about all adds/updates/removals
|
|
for (AZ::EntityId id : entityAdds)
|
|
{
|
|
SliceTransaction::Result result = transaction->AddEntity(id);
|
|
if (!result)
|
|
{
|
|
gEnv->pLog->LogWarning("Error adding entity with ID %s to slice: %s\n\n%s",
|
|
id.ToString().c_str(), filename.toUtf8().data(), result.GetError().c_str());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
for (AZ::EntityId id : entityRemovals)
|
|
{
|
|
SliceTransaction::Result result = transaction->RemoveEntity(id);
|
|
if (!result)
|
|
{
|
|
gEnv->pLog->LogWarning("Error removing entity with ID %s from slice: %s\n\n%s",
|
|
id.ToString().c_str(), filename.toUtf8().data(), result.GetError().c_str());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
for (AZ::EntityId id : entityUpdates)
|
|
{
|
|
SliceTransaction::Result result = transaction->UpdateEntity(id);
|
|
if (!result)
|
|
{
|
|
gEnv->pLog->LogWarning("Error updating entity with ID %s in slice: %s\n\n%s",
|
|
id.ToString().c_str(), filename.toUtf8().data(), result.GetError().c_str());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Commit
|
|
SliceTransaction::Result commitResult = transaction->Commit(
|
|
targetAssetId,
|
|
SlicePreSaveCallbackForWorldEntities,
|
|
nullptr,
|
|
SliceTransaction::SliceCommitFlags::DisableUndoCapture);
|
|
|
|
if (!commitResult)
|
|
{
|
|
gEnv->pLog->LogWarning("Failed to to save slice \"%s\".\n\nError:\n%s",
|
|
filename.toUtf8().data(), commitResult.GetError().c_str());
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CCryEditDoc::LoadEntitiesFromLevel(const QString& levelPakFile)
|
|
{
|
|
bool isPrefabEnabled = false;
|
|
AzFramework::ApplicationRequests::Bus::BroadcastResult(isPrefabEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
|
|
|
|
bool loadedSuccessfully = false;
|
|
|
|
if (!isPrefabEnabled)
|
|
{
|
|
auto pakSystem = GetIEditor()->GetSystem()->GetIPak();
|
|
bool pakOpened = pakSystem->OpenPack(levelPakFile.toUtf8().data());
|
|
if (pakOpened)
|
|
{
|
|
const QString entityFilename = Path::GetPath(levelPakFile) + "levelentities.editor_xml";
|
|
|
|
CCryFile entitiesFile;
|
|
if (entitiesFile.Open(entityFilename.toUtf8().data(), "rt"))
|
|
{
|
|
AZStd::vector<char> fileBuffer;
|
|
fileBuffer.resize(entitiesFile.GetLength());
|
|
if (!fileBuffer.empty())
|
|
{
|
|
if (fileBuffer.size() == entitiesFile.ReadRaw(fileBuffer.begin(), fileBuffer.size()))
|
|
{
|
|
AZ::IO::ByteContainerStream<AZStd::vector<char>> fileStream(&fileBuffer);
|
|
|
|
EBUS_EVENT_RESULT(
|
|
loadedSuccessfully, AzToolsFramework::EditorEntityContextRequestBus, LoadFromStreamWithLayers, fileStream,
|
|
levelPakFile);
|
|
}
|
|
else
|
|
{
|
|
AZ_Error(
|
|
"Editor", false, "Failed to load level entities because the file \"%s\" could not be read.",
|
|
entityFilename.toUtf8().data());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AZ_Error(
|
|
"Editor", false, "Failed to load level entities because the file \"%s\" is empty.", entityFilename.toUtf8().data());
|
|
}
|
|
|
|
entitiesFile.Close();
|
|
}
|
|
else
|
|
{
|
|
AZ_Error(
|
|
"Editor", false, "Failed to load level entities because the file \"%s\" was not found.",
|
|
entityFilename.toUtf8().data());
|
|
}
|
|
|
|
pakSystem->ClosePack(levelPakFile.toUtf8().data());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
|
|
AZ_Assert(fileIO, "No File IO implementation available");
|
|
|
|
AZ::IO::HandleType fileHandle;
|
|
AZ::IO::Result openResult = fileIO->Open(levelPakFile.toUtf8().data(), AZ::IO::OpenMode::ModeRead | AZ::IO::OpenMode::ModeBinary, fileHandle);
|
|
if (openResult)
|
|
{
|
|
AZ::IO::FileIOStream stream(fileHandle, AZ::IO::OpenMode::ModeRead | AZ::IO::OpenMode::ModeBinary, false);
|
|
AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult(
|
|
loadedSuccessfully, &AzToolsFramework::EditorEntityContextRequests::LoadFromStreamWithLayers, stream, levelPakFile);
|
|
stream.Close();
|
|
}
|
|
}
|
|
|
|
return loadedSuccessfully;
|
|
}
|
|
|
|
bool CCryEditDoc::LoadEntitiesFromSlice(const QString& sliceFile)
|
|
{
|
|
bool sliceLoaded = false;
|
|
{
|
|
AZ::IO::FileIOStream sliceFileStream(sliceFile.toUtf8().data(), AZ::IO::OpenMode::ModeRead);
|
|
if (!sliceFileStream.IsOpen())
|
|
{
|
|
AZ_Error("Editor", false, "Failed to load entities because the file \"%s\" could not be read.", sliceFile.toUtf8().data());
|
|
return false;
|
|
}
|
|
|
|
AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult(sliceLoaded, &AzToolsFramework::EditorEntityContextRequestBus::Events::LoadFromStream, sliceFileStream);
|
|
}
|
|
|
|
if (!sliceLoaded)
|
|
{
|
|
AZ_Error("Editor", false, "Failed to load entities from slice file \"%s\"", sliceFile.toUtf8().data());
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CCryEditDoc::LoadLevel(TDocMultiArchive& arrXmlAr, const QString& absoluteCryFilePath)
|
|
{
|
|
bool isPrefabEnabled = false;
|
|
AzFramework::ApplicationRequests::Bus::BroadcastResult(isPrefabEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
|
|
|
|
auto pIPak = GetIEditor()->GetSystem()->GetIPak();
|
|
|
|
QString folderPath = QFileInfo(absoluteCryFilePath).absolutePath();
|
|
|
|
OnStartLevelResourceList();
|
|
|
|
// Load next level resource list.
|
|
if (!isPrefabEnabled)
|
|
{
|
|
pIPak->GetResourceList(AZ::IO::IArchive::RFOM_NextLevel)->Load(Path::Make(folderPath, "resourcelist.txt").toUtf8().data());
|
|
}
|
|
|
|
GetIEditor()->Notify(eNotify_OnBeginLoad);
|
|
CrySystemEventBus::Broadcast(&CrySystemEventBus::Events::OnCryEditorBeginLoad);
|
|
//GetISystem()->GetISystemEventDispatcher()->OnSystemEvent( ESYSTEM_EVENT_LEVEL_LOAD_START,0,0 );
|
|
DeleteContents();
|
|
|
|
// Set level path directly *after* DeleteContents(), since that will unload the previous level and clear the level path.
|
|
GetIEditor()->GetGameEngine()->SetLevelPath(folderPath);
|
|
|
|
SetModifiedFlag(true); // dirty during de-serialize
|
|
SetModifiedModules(eModifiedAll);
|
|
Load(arrXmlAr, absoluteCryFilePath);
|
|
|
|
GetISystem()->GetISystemEventDispatcher()->OnSystemEvent(ESYSTEM_EVENT_LEVEL_LOAD_END, 0, 0);
|
|
// We don't need next level resource list anymore.
|
|
if (!isPrefabEnabled)
|
|
{
|
|
pIPak->GetResourceList(AZ::IO::IArchive::RFOM_NextLevel)->Clear();
|
|
}
|
|
SetModifiedFlag(false); // start off with unmodified
|
|
SetModifiedModules(eModifiedNothing);
|
|
SetDocumentReady(true);
|
|
GetIEditor()->Notify(eNotify_OnEndLoad);
|
|
CrySystemEventBus::Broadcast(&CrySystemEventBus::Events::OnCryEditorEndLoad);
|
|
|
|
GetIEditor()->SetStatusText("Ready");
|
|
|
|
return true;
|
|
}
|
|
|
|
void CCryEditDoc::Hold(const QString& holdName)
|
|
{
|
|
Hold(holdName, holdName);
|
|
}
|
|
|
|
void CCryEditDoc::Hold(const QString& holdName, const QString& relativeHoldPath)
|
|
{
|
|
if (!IsDocumentReady() || GetEditMode() == CCryEditDoc::DocumentEditingMode::SliceEdit)
|
|
{
|
|
return;
|
|
}
|
|
|
|
QString levelPath = GetIEditor()->GetGameEngine()->GetLevelPath();
|
|
char resolvedLevelPath[AZ_MAX_PATH_LEN] = { 0 };
|
|
AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(levelPath.toUtf8().data(), resolvedLevelPath, AZ_MAX_PATH_LEN);
|
|
|
|
QString holdPath = QString::fromUtf8(resolvedLevelPath) + "/" + relativeHoldPath + "/";
|
|
QString holdFilename = holdPath + holdName + GetIEditor()->GetGameEngine()->GetLevelExtension();
|
|
|
|
// never auto-backup while we're trying to hold.
|
|
bool oldBackup = gSettings.bBackupOnSave;
|
|
gSettings.bBackupOnSave = false;
|
|
SaveLevel(holdFilename);
|
|
gSettings.bBackupOnSave = oldBackup;
|
|
|
|
GetIEditor()->GetGameEngine()->SetLevelPath(levelPath);
|
|
}
|
|
|
|
void CCryEditDoc::Fetch(const QString& relativeHoldPath, bool bShowMessages, bool bDelHoldFolder)
|
|
{
|
|
Fetch(relativeHoldPath, relativeHoldPath, bShowMessages, bDelHoldFolder ? FetchPolicy::DELETE_FOLDER : FetchPolicy::PRESERVE);
|
|
}
|
|
|
|
void CCryEditDoc::Fetch(const QString& holdName, const QString& relativeHoldPath, bool bShowMessages, FetchPolicy policy)
|
|
{
|
|
if (!IsDocumentReady() || GetEditMode() == CCryEditDoc::DocumentEditingMode::SliceEdit)
|
|
{
|
|
return;
|
|
}
|
|
|
|
QString levelPath = GetIEditor()->GetGameEngine()->GetLevelPath();
|
|
char resolvedLevelPath[AZ_MAX_PATH_LEN] = { 0 };
|
|
AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(levelPath.toUtf8().data(), resolvedLevelPath, AZ_MAX_PATH_LEN);
|
|
|
|
QString holdPath = QString::fromUtf8(resolvedLevelPath) + "/" + relativeHoldPath + "/";
|
|
QString holdFilename = holdPath + holdName + GetIEditor()->GetGameEngine()->GetLevelExtension();
|
|
|
|
{
|
|
QFile cFile(holdFilename);
|
|
// Open the file for writing, create it if needed
|
|
if (!cFile.open(QFile::ReadOnly))
|
|
{
|
|
if (bShowMessages)
|
|
{
|
|
QMessageBox::information(QApplication::activeWindow(), QString(), QObject::tr("You have to use 'Hold' before you can fetch!"));
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Does the document contain unsaved data ?
|
|
if (bShowMessages && IsModified() &&
|
|
QMessageBox::question(QApplication::activeWindow(), QString(), QObject::tr("The document contains unsaved data, it will be lost if fetched.\r\nReally fetch old state?")) != QMessageBox::Yes)
|
|
{
|
|
return;
|
|
}
|
|
|
|
GetIEditor()->FlushUndo();
|
|
|
|
TDocMultiArchive arrXmlAr = {};
|
|
if (!LoadXmlArchiveArray(arrXmlAr, holdFilename, holdPath))
|
|
{
|
|
QMessageBox::critical(QApplication::activeWindow(), "Error", "The temporary 'Hold' level failed to load successfully. Your level might be corrupted, you should restart the Editor.", QMessageBox::Ok);
|
|
AZ_Error("EditDoc", false, "Fetch failed to load the Xml Archive");
|
|
return;
|
|
}
|
|
|
|
// Load the state
|
|
LoadLevel(arrXmlAr, holdFilename);
|
|
|
|
// Load AZ entities for the editor.
|
|
LoadEntitiesFromLevel(holdFilename);
|
|
|
|
GetIEditor()->GetGameEngine()->SetLevelPath(levelPath);
|
|
|
|
GetIEditor()->FlushUndo();
|
|
|
|
switch (policy)
|
|
{
|
|
case FetchPolicy::DELETE_FOLDER:
|
|
CFileUtil::Deltree(holdPath.toUtf8().data(), true);
|
|
break;
|
|
|
|
case FetchPolicy::DELETE_LY_FILE:
|
|
CFileUtil::DeleteFile(holdFilename);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
namespace {
|
|
struct SFolderTime
|
|
{
|
|
QString folder;
|
|
time_t creationTime;
|
|
};
|
|
|
|
bool SortByCreationTime(SFolderTime& a, SFolderTime& b)
|
|
{
|
|
return a.creationTime < b.creationTime;
|
|
}
|
|
|
|
// This function, given a source folder to scan, returns all folders within that folder
|
|
// non-recursively. They will be sorted by time, with most recent first, and oldest last.
|
|
void CollectAllFoldersByTime(const char* sourceFolder, std::vector<SFolderTime>& outputFolders)
|
|
{
|
|
QString folderMask(sourceFolder);
|
|
AZ::IO::ArchiveFileIterator handle = gEnv->pCryPak->FindFirst((folderMask + "/*").toUtf8().data());
|
|
if (handle)
|
|
{
|
|
do
|
|
{
|
|
if (handle.m_filename.front() == '.')
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ((handle.m_fileDesc.nAttrib & AZ::IO::FileDesc::Attribute::Subdirectory) == AZ::IO::FileDesc::Attribute::Subdirectory)
|
|
{
|
|
SFolderTime ft;
|
|
ft.folder = QString::fromUtf8(handle.m_filename.data(), aznumeric_cast<int>(handle.m_filename.size()));
|
|
ft.creationTime = handle.m_fileDesc.tCreate;
|
|
outputFolders.push_back(ft);
|
|
}
|
|
} while (handle = gEnv->pCryPak->FindNext(handle));
|
|
|
|
gEnv->pCryPak->FindClose(handle);
|
|
}
|
|
std::sort(outputFolders.begin(), outputFolders.end(), SortByCreationTime);
|
|
}
|
|
}
|
|
|
|
bool CCryEditDoc::BackupBeforeSave(bool force)
|
|
{
|
|
// This function will copy the contents of an entire level folder to a backup folder
|
|
// and delete older ones based on user preferences.
|
|
if (!force && !gSettings.bBackupOnSave)
|
|
{
|
|
return true; // not an error
|
|
}
|
|
|
|
QString levelPath = GetIEditor()->GetGameEngine()->GetLevelPath();
|
|
if (levelPath.isEmpty())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
char resolvedLevelPath[AZ_MAX_PATH_LEN] = { 0 };
|
|
AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(levelPath.toUtf8().data(), resolvedLevelPath, AZ_MAX_PATH_LEN);
|
|
QWaitCursor wait;
|
|
|
|
QString saveBackupPath = QString::fromUtf8(resolvedLevelPath) + "/" + kSaveBackupFolder;
|
|
|
|
std::vector<SFolderTime> folders;
|
|
CollectAllFoldersByTime(saveBackupPath.toUtf8().data(), folders);
|
|
|
|
for (int i = int(folders.size()) - gSettings.backupOnSaveMaxCount; i >= 0; --i)
|
|
{
|
|
CFileUtil::Deltree(QStringLiteral("%1/%2/").arg(saveBackupPath, folders[i].folder).toUtf8().data(), true);
|
|
}
|
|
|
|
QDateTime theTime = QDateTime::currentDateTime();
|
|
QString subFolder = theTime.toString("yyyy-MM-dd [HH.mm.ss]");
|
|
|
|
QString levelName = GetIEditor()->GetGameEngine()->GetLevelName();
|
|
QString backupPath = saveBackupPath + "/" + subFolder;
|
|
AZ::IO::FileIOBase::GetDirectInstance()->CreatePath(backupPath.toUtf8().data());
|
|
|
|
QString sourcePath = QString::fromUtf8(resolvedLevelPath) + "/";
|
|
|
|
QString ignoredFiles;
|
|
|
|
for (const char* backupOrTempFolderName : kBackupOrTempFolders)
|
|
{
|
|
if (!ignoredFiles.isEmpty())
|
|
{
|
|
ignoredFiles += "|";
|
|
}
|
|
ignoredFiles += QString::fromUtf8(backupOrTempFolderName);
|
|
}
|
|
|
|
// copy that whole tree:
|
|
AZ_TracePrintf("Editor", "Saving level backup to '%s'...\n", backupPath.toUtf8().data());
|
|
if (IFileUtil::ETREECOPYOK != CFileUtil::CopyTree(sourcePath, backupPath, true, false, ignoredFiles.toUtf8().data()))
|
|
{
|
|
gEnv->pLog->LogWarning("Attempting to save backup to %s before saving, but could not write all files.", backupPath.toUtf8().data());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void CCryEditDoc::SaveAutoBackup(bool bForce)
|
|
{
|
|
if (!bForce && (!gSettings.autoBackupEnabled || GetIEditor()->IsInGameMode()))
|
|
{
|
|
return;
|
|
}
|
|
|
|
QString levelPath = GetIEditor()->GetGameEngine()->GetLevelPath();
|
|
if (levelPath.isEmpty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
static bool isInProgress = false;
|
|
if (isInProgress)
|
|
{
|
|
return;
|
|
}
|
|
|
|
isInProgress = true;
|
|
|
|
QWaitCursor wait;
|
|
|
|
QString autoBackupPath = levelPath + "/" + kAutoBackupFolder;
|
|
|
|
// collect all subfolders
|
|
std::vector<SFolderTime> folders;
|
|
|
|
CollectAllFoldersByTime(autoBackupPath.toUtf8().data(), folders);
|
|
|
|
for (int i = int(folders.size()) - gSettings.autoBackupMaxCount; i >= 0; --i)
|
|
{
|
|
CFileUtil::Deltree(QStringLiteral("%1/%2/").arg(autoBackupPath, folders[i].folder).toUtf8().data(), true);
|
|
}
|
|
|
|
// save new backup
|
|
QDateTime theTime = QDateTime::currentDateTime();
|
|
QString subFolder = theTime.toString(QStringLiteral("yyyy-MM-dd [HH.mm.ss]"));
|
|
|
|
QString levelName = GetIEditor()->GetGameEngine()->GetLevelName();
|
|
QString filename = autoBackupPath + "/" + subFolder + "/" + levelName + "/" + levelName + GetIEditor()->GetGameEngine()->GetLevelExtension();
|
|
SaveLevel(filename);
|
|
GetIEditor()->GetGameEngine()->SetLevelPath(levelPath);
|
|
|
|
isInProgress = false;
|
|
}
|
|
|
|
bool CCryEditDoc::IsLevelExported() const
|
|
{
|
|
return m_boLevelExported;
|
|
}
|
|
|
|
void CCryEditDoc::SetLevelExported(bool boExported)
|
|
{
|
|
m_boLevelExported = boExported;
|
|
}
|
|
|
|
void CCryEditDoc::RegisterListener(IDocListener* listener)
|
|
{
|
|
if (listener == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (std::find(m_listeners.begin(), m_listeners.end(), listener) == m_listeners.end())
|
|
{
|
|
m_listeners.push_back(listener);
|
|
}
|
|
}
|
|
|
|
void CCryEditDoc::UnregisterListener(IDocListener* listener)
|
|
{
|
|
m_listeners.remove(listener);
|
|
}
|
|
|
|
void CCryEditDoc::LogLoadTime(int time) const
|
|
{
|
|
QString appFilePath = QDir::toNativeSeparators(QCoreApplication::applicationFilePath());
|
|
QString exePath = Path::GetPath(appFilePath);
|
|
QString filename = Path::Make(exePath, "LevelLoadTime.log");
|
|
QString level = GetIEditor()->GetGameEngine()->GetLevelPath();
|
|
|
|
CLogFile::FormatLine("[LevelLoadTime] Level %s loaded in %d seconds", level.toUtf8().data(), time / 1000);
|
|
#if defined(AZ_PLATFORM_WINDOWS)
|
|
SetFileAttributesW(filename.toStdWString().c_str(), FILE_ATTRIBUTE_ARCHIVE);
|
|
#endif
|
|
|
|
QFile file(filename);
|
|
if (!file.open(QFile::Append | QFile::Text))
|
|
{
|
|
return;
|
|
}
|
|
|
|
char version[50];
|
|
GetIEditor()->GetFileVersion().ToShortString(version, AZ_ARRAY_SIZE(version));
|
|
|
|
time = time / 1000;
|
|
QString text = QStringLiteral("\n[%1] Level %2 loaded in %3 seconds").arg(version, level).arg(time);
|
|
file.write(text.toUtf8());
|
|
}
|
|
|
|
void CCryEditDoc::SetDocumentReady(bool bReady)
|
|
{
|
|
m_bDocumentReady = bReady;
|
|
}
|
|
|
|
void CCryEditDoc::RegisterConsoleVariables()
|
|
{
|
|
doc_validate_surface_types = gEnv->pConsole->GetCVar("doc_validate_surface_types");
|
|
|
|
if (!doc_validate_surface_types)
|
|
{
|
|
doc_validate_surface_types = REGISTER_INT_CB("doc_validate_surface_types", 0, 0,
|
|
"Flag indicating whether icons are displayed on the animation graph.\n"
|
|
"Default is 1.\n",
|
|
OnValidateSurfaceTypesChanged);
|
|
}
|
|
}
|
|
|
|
void CCryEditDoc::OnValidateSurfaceTypesChanged(ICVar*)
|
|
{
|
|
CErrorsRecorder errorsRecorder(GetIEditor());
|
|
CSurfaceTypeValidator().Validate();
|
|
}
|
|
|
|
void CCryEditDoc::OnStartLevelResourceList()
|
|
{
|
|
// after loading another level we clear the RFOM_Level list, the first time the list should be empty
|
|
static bool bFirstTime = true;
|
|
|
|
if (bFirstTime)
|
|
{
|
|
const char* pResFilename = gEnv->pCryPak->GetResourceList(AZ::IO::IArchive::RFOM_Level)->GetFirst();
|
|
|
|
while (pResFilename)
|
|
{
|
|
// This should be fixed because ExecuteCommandLine is executed right after engine init as we assume the
|
|
// engine already has all data loaded an is initialized to process commands. Loading data afterwards means
|
|
// some init was done later which can cause problems when running in the engine batch mode (executing console commands).
|
|
gEnv->pLog->LogError("'%s' was loaded after engine init but before level load/new (should be fixed)", pResFilename);
|
|
pResFilename = gEnv->pCryPak->GetResourceList(AZ::IO::IArchive::RFOM_Level)->GetNext();
|
|
}
|
|
|
|
bFirstTime = false;
|
|
}
|
|
|
|
gEnv->pCryPak->GetResourceList(AZ::IO::IArchive::RFOM_Level)->Clear();
|
|
}
|
|
|
|
bool CCryEditDoc::DoFileSave()
|
|
{
|
|
if (GetEditMode() == CCryEditDoc::DocumentEditingMode::LevelEdit)
|
|
{
|
|
// If the file to save is the temporary level it should 'save as' since temporary levels will get deleted
|
|
const char* temporaryLevelName = GetTemporaryLevelName();
|
|
if (QString::compare(GetIEditor()->GetLevelName(), temporaryLevelName) == 0)
|
|
{
|
|
QString filename;
|
|
if (CCryEditApp::instance()->GetDocManager()->DoPromptFileName(filename, ID_FILE_SAVE_AS, 0, false, nullptr)
|
|
&& !filename.isEmpty() && !QFileInfo(filename).exists())
|
|
{
|
|
if (SaveLevel(filename))
|
|
{
|
|
DeleteTemporaryLevel();
|
|
QString newLevelPath = filename.left(filename.lastIndexOf('/') + 1);
|
|
GetIEditor()->GetDocument()->SetPathName(filename);
|
|
GetIEditor()->GetGameEngine()->SetLevelPath(newLevelPath);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
if (!IsDocumentReady())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return Internal::SaveLevel();
|
|
}
|
|
|
|
const char* CCryEditDoc::GetTemporaryLevelName() const
|
|
{
|
|
return gEnv->pConsole->GetCVar("g_TemporaryLevelName")->GetString();
|
|
}
|
|
|
|
void CCryEditDoc::DeleteTemporaryLevel()
|
|
{
|
|
QString tempLevelPath = (Path::GetEditingGameDataFolder() + "/Levels/" + GetTemporaryLevelName()).c_str();
|
|
GetIEditor()->GetSystem()->GetIPak()->ClosePacks(tempLevelPath.toUtf8().data());
|
|
CFileUtil::Deltree(tempLevelPath.toUtf8().data(), true);
|
|
}
|
|
|
|
void CCryEditDoc::InitEmptyLevel(int /*resolution*/, int /*unitSize*/, bool /*bUseTerrain*/)
|
|
{
|
|
GetIEditor()->SetStatusText("Initializing Level...");
|
|
|
|
OnStartLevelResourceList();
|
|
|
|
GetIEditor()->Notify(eNotify_OnBeginNewScene);
|
|
CLogFile::WriteLine("Preparing new document...");
|
|
|
|
//cleanup resources!
|
|
GetISystem()->GetISystemEventDispatcher()->OnSystemEvent(ESYSTEM_EVENT_LEVEL_POST_UNLOAD, 0, 0);
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Initialize defaults.
|
|
//////////////////////////////////////////////////////////////////////////
|
|
if (!GetIEditor()->IsInPreviewMode())
|
|
{
|
|
GetIEditor()->ReloadTemplates();
|
|
m_environmentTemplate = GetIEditor()->FindTemplate("Environment");
|
|
|
|
GetIEditor()->GetGameEngine()->SetLevelCreated(true);
|
|
GetIEditor()->GetGameEngine()->SetLevelCreated(false);
|
|
}
|
|
|
|
{
|
|
// Notify listeners.
|
|
std::list<IDocListener*> listeners = m_listeners;
|
|
for (IDocListener* listener : listeners)
|
|
{
|
|
listener->OnNewDocument();
|
|
}
|
|
}
|
|
|
|
// Tell the system that the level has been created/loaded.
|
|
GetISystem()->GetISystemEventDispatcher()->OnSystemEvent(ESYSTEM_EVENT_LEVEL_LOAD_END, 0, 0);
|
|
|
|
GetIEditor()->Notify(eNotify_OnEndNewScene);
|
|
SetModifiedFlag(false);
|
|
SetLevelExported(false);
|
|
SetModifiedModules(eModifiedNothing);
|
|
|
|
GetIEditor()->SetStatusText("Ready");
|
|
}
|
|
|
|
void CCryEditDoc::CreateDefaultLevelAssets([[maybe_unused]] int resolution, [[maybe_unused]] int unitSize)
|
|
{
|
|
AzToolsFramework::EditorLevelNotificationBus::Broadcast(&AzToolsFramework::EditorLevelNotificationBus::Events::OnNewLevelCreated);
|
|
}
|
|
|
|
void CCryEditDoc::OnEnvironmentPropertyChanged(IVariable* pVar)
|
|
{
|
|
if (pVar == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
XmlNodeRef node = GetEnvironmentTemplate();
|
|
if (node == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// QVariant will not convert a void * to int, so do it manually.
|
|
int nKey = static_cast<int>(reinterpret_cast<intptr_t>(pVar->GetUserData().value<void*>()));
|
|
|
|
int nGroup = (nKey & 0xFFFF0000) >> 16;
|
|
int nChild = (nKey & 0x0000FFFF);
|
|
|
|
if (nGroup < 0 || nGroup >= node->getChildCount())
|
|
{
|
|
return;
|
|
}
|
|
|
|
XmlNodeRef groupNode = node->getChild(nGroup);
|
|
|
|
if (groupNode == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (nChild < 0 || nChild >= groupNode->getChildCount())
|
|
{
|
|
return;
|
|
}
|
|
|
|
XmlNodeRef childNode = groupNode->getChild(nChild);
|
|
if (childNode == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
QString childValue;
|
|
|
|
if (pVar->GetDataType() == IVariable::DT_COLOR)
|
|
{
|
|
Vec3 value;
|
|
pVar->Get(value);
|
|
QColor gammaColor = ColorLinearToGamma(ColorF(value.x, value.y, value.z));
|
|
childValue = QStringLiteral("%1,%2,%3").arg(gammaColor.red()).arg(gammaColor.green()).arg(gammaColor.blue());
|
|
}
|
|
else
|
|
{
|
|
pVar->Get(childValue);
|
|
}
|
|
childNode->setAttr("value", childValue.toUtf8().data());
|
|
}
|
|
|
|
QString CCryEditDoc::GetCryIndexPath(const char* levelFilePath) const
|
|
{
|
|
QString levelPath = Path::GetPath(levelFilePath);
|
|
QString levelName = Path::GetFileName(levelFilePath);
|
|
return Path::AddPathSlash(levelPath + levelName + "_editor");
|
|
}
|
|
|
|
bool CCryEditDoc::LoadXmlArchiveArray(TDocMultiArchive& arrXmlAr, const QString& absoluteLevelPath, const QString& levelPath)
|
|
{
|
|
auto pIPak = GetIEditor()->GetSystem()->GetIPak();
|
|
|
|
//if (m_pSWDoc->IsNull())
|
|
{
|
|
CXmlArchive* pXmlAr = new CXmlArchive();
|
|
if (!pXmlAr)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
CXmlArchive& xmlAr = *pXmlAr;
|
|
xmlAr.bLoading = true;
|
|
|
|
// bound to the level folder, as if it were the assets folder.
|
|
// this mounts (whateverlevelname.ly) as @products@/Levels/whateverlevelname/ and thus it works...
|
|
bool openLevelPakFileSuccess = pIPak->OpenPack(levelPath.toUtf8().data(), absoluteLevelPath.toUtf8().data());
|
|
if (!openLevelPakFileSuccess)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
CPakFile pakFile;
|
|
bool loadFromPakSuccess = xmlAr.LoadFromPak(levelPath, pakFile);
|
|
pIPak->ClosePack(absoluteLevelPath.toUtf8().data());
|
|
if (!loadFromPakSuccess)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FillXmlArArray(arrXmlAr, &xmlAr);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CCryEditDoc::ReleaseXmlArchiveArray(TDocMultiArchive& arrXmlAr)
|
|
{
|
|
SAFE_DELETE(arrXmlAr[0]);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// AzToolsFramework::EditorEntityContextNotificationBus interface implementation
|
|
void CCryEditDoc::OnSliceInstantiated(const AZ::Data::AssetId& sliceAssetId, AZ::SliceComponent::SliceInstanceAddress& sliceAddress, const AzFramework::SliceInstantiationTicket& /*ticket*/)
|
|
{
|
|
if (m_envProbeSliceAssetId == sliceAssetId)
|
|
{
|
|
const AZ::SliceComponent::EntityList& entities = sliceAddress.GetInstance()->GetInstantiated()->m_entities;
|
|
const AZ::Uuid editorEnvProbeComponentId("{8DBD6035-583E-409F-AFD9-F36829A0655D}");
|
|
AzToolsFramework::EntityIdList entityIds;
|
|
entityIds.reserve(entities.size());
|
|
for (const AZ::Entity* entity : entities)
|
|
{
|
|
if (entity->FindComponent(editorEnvProbeComponentId))
|
|
{
|
|
// Update Probe Area size to cover the whole terrain
|
|
LmbrCentral::EditorLightComponentRequestBus::Event(entity->GetId(), &LmbrCentral::EditorLightComponentRequests::SetProbeAreaDimensions, AZ::Vector3(m_terrainSize, m_terrainSize, m_envProbeHeight));
|
|
|
|
// Force update the light to apply cubemap
|
|
LmbrCentral::EditorLightComponentRequestBus::Event(entity->GetId(), &LmbrCentral::EditorLightComponentRequests::RefreshLight);
|
|
}
|
|
entityIds.push_back(entity->GetId());
|
|
}
|
|
|
|
//Detach instantiated env probe entities from engine slice
|
|
AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Broadcast(
|
|
&AzToolsFramework::SliceEditorEntityOwnershipServiceRequests::DetachSliceEntities, entityIds);
|
|
|
|
sliceAddress.SetInstance(nullptr);
|
|
sliceAddress.SetReference(nullptr);
|
|
SetModifiedFlag(true);
|
|
SetModifiedModules(eModifiedEntities);
|
|
|
|
AzToolsFramework::SliceEditorEntityOwnershipServiceNotificationBus::Handler::BusDisconnect();
|
|
|
|
//save after level default slice fully instantiated
|
|
Save();
|
|
}
|
|
GetIEditor()->ResumeUndo();
|
|
}
|
|
|
|
|
|
void CCryEditDoc::OnSliceInstantiationFailed(const AZ::Data::AssetId& sliceAssetId, const AzFramework::SliceInstantiationTicket& /*ticket*/)
|
|
{
|
|
if (m_envProbeSliceAssetId == sliceAssetId)
|
|
{
|
|
AzToolsFramework::SliceEditorEntityOwnershipServiceNotificationBus::Handler::BusDisconnect();
|
|
AZ_Warning("Editor", false, "Failed to instantiate default environment probe slice.");
|
|
}
|
|
GetIEditor()->ResumeUndo();
|
|
}
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
namespace AzToolsFramework
|
|
{
|
|
void CryEditDocFuncsHandler::Reflect(AZ::ReflectContext* context)
|
|
{
|
|
if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
|
|
{
|
|
// this will put these methods into the 'azlmbr.legacy.general' module
|
|
auto addLegacyGeneral = [](AZ::BehaviorContext::GlobalMethodBuilder methodBuilder)
|
|
{
|
|
methodBuilder->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
|
|
->Attribute(AZ::Script::Attributes::Category, "Legacy/Editor")
|
|
->Attribute(AZ::Script::Attributes::Module, "legacy.general");
|
|
};
|
|
addLegacyGeneral(behaviorContext->Method("save_level", ::Internal::SaveLevel, nullptr, "Saves the current level."));
|
|
}
|
|
}
|
|
}
|
|
|
|
#include <moc_CryEditDoc.cpp>
|