CryEditDoc.cpp 49 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include "EditorDefs.h"
  9. #include "CryEditDoc.h"
  10. // Qt
  11. #include <QDateTime>
  12. #include <QDialogButtonBox>
  13. // AzCore
  14. #include <AzCore/Component/TransformBus.h>
  15. #include <AzCore/Asset/AssetManager.h>
  16. #include <AzCore/Interface/Interface.h>
  17. #include <AzCore/Time/ITime.h>
  18. #include <AzCore/Utils/Utils.h>
  19. #include <MathConversion.h>
  20. // AzFramework
  21. #include <AzFramework/Archive/IArchive.h>
  22. #include <AzFramework/API/ApplicationAPI.h>
  23. // AzToolsFramework
  24. #include <AzToolsFramework/ComponentMode/EditorComponentModeBus.h>
  25. #include <AzToolsFramework/UI/UICore/WidgetHelpers.h>
  26. #include <AzToolsFramework/API/EditorLevelNotificationBus.h>
  27. // Editor
  28. #include "Settings.h"
  29. #include "PluginManager.h"
  30. #include "Util/Variable.h"
  31. #include "ViewManager.h"
  32. #include "DisplaySettings.h"
  33. #include "GameEngine.h"
  34. #include "CryEdit.h"
  35. #include "Util/PakFile.h"
  36. #include "ErrorReportDialog.h"
  37. #include "Util/AutoLogTime.h"
  38. #include "CheckOutDialog.h"
  39. #include "MainWindow.h"
  40. #include "LevelFileDialog.h"
  41. #include "Undo/Undo.h"
  42. #include <Atom/RPI.Public/ViewportContext.h>
  43. #include <Atom/RPI.Public/ViewportContextBus.h>
  44. // LmbrCentral
  45. #include <LmbrCentral/Audio/AudioSystemComponentBus.h>
  46. static const char* kAutoBackupFolder = "_autobackup";
  47. static const char* kHoldFolder = "$tmp_hold"; // conform to the ignored file types $tmp[0-9]*_ regex
  48. static const char* kSaveBackupFolder = "_savebackup";
  49. static const char* kResizeTempFolder = "$tmp_resize"; // conform to the ignored file types $tmp[0-9]*_ regex
  50. static const char* kBackupOrTempFolders[] =
  51. {
  52. kAutoBackupFolder,
  53. kHoldFolder,
  54. kSaveBackupFolder,
  55. kResizeTempFolder,
  56. "_hold", // legacy name
  57. "_tmpresize", // legacy name
  58. };
  59. namespace Internal
  60. {
  61. bool SaveLevel()
  62. {
  63. if (!GetIEditor()->GetDocument()->DoSave(GetIEditor()->GetDocument()->GetActivePathName(), true))
  64. {
  65. return false;
  66. }
  67. return true;
  68. }
  69. }
  70. /////////////////////////////////////////////////////////////////////////////
  71. // CCryEditDoc construction/destruction
  72. CCryEditDoc::CCryEditDoc()
  73. : m_modifiedModuleFlags(eModifiedNothing)
  74. {
  75. ////////////////////////////////////////////////////////////////////////
  76. // Set member variables to initial values
  77. ////////////////////////////////////////////////////////////////////////
  78. m_fogTemplate = GetIEditor()->FindTemplate("Fog");
  79. m_environmentTemplate = GetIEditor()->FindTemplate("Environment");
  80. if (m_environmentTemplate)
  81. {
  82. m_fogTemplate = m_environmentTemplate->findChild("Fog");
  83. }
  84. else
  85. {
  86. m_environmentTemplate = XmlHelpers::CreateXmlNode("Environment");
  87. }
  88. GetIEditor()->SetDocument(this);
  89. CLogFile::WriteLine("Document created");
  90. m_prefabSystemComponentInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabSystemComponentInterface>::Get();
  91. AZ_Assert(m_prefabSystemComponentInterface, "PrefabSystemComponentInterface is not found.");
  92. m_prefabEditorEntityOwnershipInterface = AZ::Interface<AzToolsFramework::PrefabEditorEntityOwnershipInterface>::Get();
  93. AZ_Assert(m_prefabEditorEntityOwnershipInterface, "PrefabEditorEntityOwnershipInterface is not found.");
  94. m_prefabLoaderInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabLoaderInterface>::Get();
  95. AZ_Assert(m_prefabLoaderInterface, "PrefabLoaderInterface is not found.");
  96. m_prefabIntegrationInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabIntegrationInterface>::Get();
  97. AZ_Assert(m_prefabIntegrationInterface, "PrefabIntegrationInterface is not found.");
  98. }
  99. CCryEditDoc::~CCryEditDoc()
  100. {
  101. GetIEditor()->SetDocument(nullptr);
  102. CLogFile::WriteLine("Document destroyed");
  103. }
  104. bool CCryEditDoc::IsModified() const
  105. {
  106. return m_modified;
  107. }
  108. void CCryEditDoc::SetModifiedFlag(bool modified)
  109. {
  110. m_modified = modified;
  111. }
  112. QString CCryEditDoc::GetLevelPathName() const
  113. {
  114. return m_pathName;
  115. }
  116. void CCryEditDoc::SetPathName(const QString& pathName)
  117. {
  118. m_pathName = pathName;
  119. SetTitle(pathName.isEmpty() ? tr("Untitled") : PathUtil::GetFileName(pathName.toUtf8().data()).c_str());
  120. }
  121. QString CCryEditDoc::GetActivePathName() const
  122. {
  123. return GetLevelPathName();
  124. }
  125. QString CCryEditDoc::GetTitle() const
  126. {
  127. return m_title;
  128. }
  129. void CCryEditDoc::SetTitle(const QString& title)
  130. {
  131. m_title = title;
  132. }
  133. bool CCryEditDoc::IsBackupOrTempLevelSubdirectory(const QString& folderName)
  134. {
  135. for (const char* backupOrTempFolderName : kBackupOrTempFolders)
  136. {
  137. if (!folderName.compare(backupOrTempFolderName, Qt::CaseInsensitive))
  138. {
  139. return true;
  140. }
  141. }
  142. return false;
  143. }
  144. bool CCryEditDoc::DoSave(const QString& pathName, bool replace)
  145. {
  146. if (!OnSaveDocument(pathName.isEmpty() ? GetActivePathName() : pathName))
  147. {
  148. return false;
  149. }
  150. if (replace)
  151. {
  152. SetPathName(pathName);
  153. }
  154. return true;
  155. }
  156. bool CCryEditDoc::Save()
  157. {
  158. return OnSaveDocument(GetActivePathName());
  159. }
  160. void CCryEditDoc::DeleteContents()
  161. {
  162. m_hasErrors = false;
  163. SetDocumentReady(false);
  164. GetIEditor()->Notify(eNotify_OnCloseScene);
  165. CrySystemEventBus::Broadcast(&CrySystemEventBus::Events::OnCryEditorCloseScene);
  166. AzToolsFramework::EditorEntityContextRequestBus::Broadcast(
  167. &AzToolsFramework::EditorEntityContextRequestBus::Events::ResetEditorContext);
  168. //////////////////////////////////////////////////////////////////////////
  169. // Clear all undo info.
  170. //////////////////////////////////////////////////////////////////////////
  171. GetIEditor()->FlushUndo();
  172. // Notify listeners.
  173. for (IDocListener* listener : m_listeners)
  174. {
  175. listener->OnCloseDocument();
  176. }
  177. GetIEditor()->ResetViews();
  178. // Load scripts data
  179. SetModifiedFlag(false);
  180. SetModifiedModules(eModifiedNothing);
  181. // Clear error reports if open.
  182. CErrorReportDialog::Clear();
  183. // Unload level specific audio binary data.
  184. LmbrCentral::AudioSystemComponentRequestBus::Broadcast(&LmbrCentral::AudioSystemComponentRequestBus::Events::LevelUnloadAudio);
  185. GetIEditor()->Notify(eNotify_OnSceneClosed);
  186. CrySystemEventBus::Broadcast(&CrySystemEventBus::Events::OnCryEditorSceneClosed);
  187. }
  188. void CCryEditDoc::Load(CXmlArchive& xmlAr, const QString& szFilename)
  189. {
  190. TDocMultiArchive arrXmlAr;
  191. FillXmlArArray(arrXmlAr, &xmlAr);
  192. CCryEditDoc::Load(arrXmlAr, szFilename);
  193. }
  194. //////////////////////////////////////////////////////////////////////////
  195. void CCryEditDoc::Load(TDocMultiArchive& /* arrXmlAr */, const QString& szFilename)
  196. {
  197. m_hasErrors = false;
  198. // Register a unique load event
  199. QString fileName = Path::GetFileName(szFilename);
  200. QString levelHash = szFilename;
  201. SEventLog loadEvent("Level_" + Path::GetFileName(fileName), "", levelHash);
  202. // Register this level and its content hash as version
  203. GetIEditor()->GetSettingsManager()->AddToolVersion(fileName, levelHash);
  204. GetIEditor()->GetSettingsManager()->RegisterEvent(loadEvent);
  205. CAutoDocNotReady autoDocNotReady;
  206. HEAP_CHECK
  207. CLogFile::FormatLine("Loading from %s...", szFilename.toUtf8().data());
  208. QString szLevelPath = Path::GetPath(szFilename);
  209. {
  210. // Set game g_levelname variable to the name of current level.
  211. QString szGameLevelName = Path::GetFileName(szFilename);
  212. ICVar* sv_map = gEnv->pConsole->GetCVar("sv_map");
  213. if (sv_map)
  214. {
  215. sv_map->Set(szGameLevelName.toUtf8().data());
  216. }
  217. }
  218. // Starts recording the opening of files using the level category
  219. if (auto archive = AZ::Interface<AZ::IO::IArchive>::Get(); archive && archive->GetRecordFileOpenList() == AZ::IO::IArchive::RFOM_EngineStartup)
  220. {
  221. archive->RecordFileOpen(AZ::IO::IArchive::RFOM_Level);
  222. }
  223. GetIEditor()->Notify(eNotify_OnBeginSceneOpen);
  224. GetIEditor()->GetMovieSystem()->RemoveAllSequences();
  225. {
  226. // Start recording errors
  227. const ICVar* pShowErrorDialogOnLoad = gEnv->pConsole->GetCVar("ed_showErrorDialogOnLoad");
  228. CErrorsRecorder errorsRecorder(pShowErrorDialogOnLoad && (pShowErrorDialogOnLoad->GetIVal() != 0));
  229. int t0 = GetTickCount();
  230. // Load level-specific audio data.
  231. AZStd::string levelFileName{ fileName.toUtf8().constData() };
  232. AZStd::to_lower(levelFileName.begin(), levelFileName.end());
  233. LmbrCentral::AudioSystemComponentRequestBus::Broadcast(
  234. &LmbrCentral::AudioSystemComponentRequestBus::Events::LevelLoadAudio, AZStd::string_view{ levelFileName });
  235. {
  236. CAutoLogTime logtime("Game Engine level load");
  237. GetIEditor()->GetGameEngine()->LoadLevel(true, true);
  238. }
  239. {
  240. CAutoLogTime logtime("Post Load");
  241. // Notify listeners.
  242. for (IDocListener* listener : m_listeners)
  243. {
  244. listener->OnLoadDocument();
  245. }
  246. }
  247. LogLoadTime(GetTickCount() - t0);
  248. // Loaded with success, remove event from log file
  249. GetIEditor()->GetSettingsManager()->UnregisterEvent(loadEvent);
  250. }
  251. GetIEditor()->Notify(eNotify_OnEndSceneOpen);
  252. }
  253. void CCryEditDoc::AfterSave()
  254. {
  255. // When saving level also save editor settings
  256. // Save settings
  257. gSettings.Save();
  258. GetIEditor()->GetDisplaySettings()->SaveRegistry();
  259. MainWindow::instance()->SaveConfig();
  260. }
  261. void CCryEditDoc::SerializeViewSettings(CXmlArchive& xmlAr)
  262. {
  263. // Load or restore the viewer settings from an XML
  264. if (xmlAr.bLoading)
  265. {
  266. bool useOldViewFormat = false;
  267. // Loading
  268. CLogFile::WriteLine("Loading View settings...");
  269. int numberOfGameViewports = GetIEditor()->GetViewManager()->GetNumberOfGameViewports();
  270. for (int i = 0; i < numberOfGameViewports; i++)
  271. {
  272. XmlNodeRef view;
  273. Vec3 vp(0.0f, 0.0f, 256.0f);
  274. Ang3 va(ZERO);
  275. auto viewName = QString("View%1").arg(i);
  276. view = xmlAr.root->findChild(viewName.toUtf8().constData());
  277. if (!view)
  278. {
  279. view = xmlAr.root->findChild("View");
  280. if (view)
  281. {
  282. useOldViewFormat = true;
  283. }
  284. }
  285. if (view)
  286. {
  287. auto viewerPosName = QString("ViewerPos%1").arg(useOldViewFormat ? "" : QString::number(i));
  288. view->getAttr(viewerPosName.toUtf8().constData(), vp);
  289. auto viewerAnglesName = QString("ViewerAngles%1").arg(useOldViewFormat ? "" : QString::number(i));
  290. view->getAttr(viewerAnglesName.toUtf8().constData(), va);
  291. }
  292. Matrix34 tm = Matrix34::CreateRotationXYZ(va);
  293. tm.SetTranslation(vp);
  294. auto viewportContextManager = AZ::Interface<AZ::RPI::ViewportContextRequestsInterface>::Get();
  295. if (auto viewportContext = viewportContextManager->GetViewportContextById(i))
  296. {
  297. viewportContext->SetCameraTransform(LYTransformToAZTransform(tm));
  298. }
  299. }
  300. }
  301. else
  302. {
  303. // Storing
  304. CLogFile::WriteLine("Storing View settings...");
  305. int numberOfGameViewports = GetIEditor()->GetViewManager()->GetNumberOfGameViewports();
  306. for (int i = 0; i < numberOfGameViewports; i++)
  307. {
  308. auto viewName = QString("View%1").arg(i);
  309. XmlNodeRef view = xmlAr.root->newChild(viewName.toUtf8().constData());
  310. CViewport* pVP = GetIEditor()->GetViewManager()->GetView(i);
  311. if (pVP)
  312. {
  313. Vec3 pos = pVP->GetViewTM().GetTranslation();
  314. Ang3 angles = Ang3::GetAnglesXYZ(Matrix33(pVP->GetViewTM()));
  315. auto viewerPosName = QString("ViewerPos%1").arg(i);
  316. view->setAttr(viewerPosName.toUtf8().constData(), pos);
  317. auto viewerAnglesName = QString("ViewerAngles%1").arg(i);
  318. view->setAttr(viewerAnglesName.toUtf8().constData(), angles);
  319. }
  320. }
  321. }
  322. }
  323. void CCryEditDoc::SerializeFogSettings(CXmlArchive& xmlAr)
  324. {
  325. if (xmlAr.bLoading)
  326. {
  327. CLogFile::WriteLine("Loading Fog settings...");
  328. XmlNodeRef fog = xmlAr.root->findChild("Fog");
  329. if (!fog)
  330. {
  331. return;
  332. }
  333. if (m_fogTemplate)
  334. {
  335. CXmlTemplate::GetValues(m_fogTemplate, fog);
  336. }
  337. }
  338. else
  339. {
  340. CLogFile::WriteLine("Storing Fog settings...");
  341. XmlNodeRef fog = xmlAr.root->newChild("Fog");
  342. if (m_fogTemplate)
  343. {
  344. CXmlTemplate::SetValues(m_fogTemplate, fog);
  345. }
  346. }
  347. }
  348. void CCryEditDoc::SetModifiedModules(EModifiedModule eModifiedModule, bool boSet)
  349. {
  350. if (!boSet)
  351. {
  352. m_modifiedModuleFlags &= ~eModifiedModule;
  353. }
  354. else
  355. {
  356. if (eModifiedModule == eModifiedNothing)
  357. {
  358. m_modifiedModuleFlags = eModifiedNothing;
  359. }
  360. else
  361. {
  362. m_modifiedModuleFlags |= eModifiedModule;
  363. }
  364. }
  365. }
  366. int CCryEditDoc::GetModifiedModule()
  367. {
  368. return m_modifiedModuleFlags;
  369. }
  370. bool CCryEditDoc::CanCloseFrame()
  371. {
  372. if (AzToolsFramework::ComponentModeFramework::InComponentMode())
  373. {
  374. AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequestBus::Broadcast(
  375. &AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequests::EndComponentMode);
  376. }
  377. // Ask the base class to ask for saving, which also includes the save
  378. // status of the plugins. Additionaly we query if all the plugins can exit
  379. // now. A reason for a failure might be that one of the plugins isn't
  380. // currently processing data or has other unsaved information which
  381. // are not serialized in the project file
  382. if (!SaveModified())
  383. {
  384. return false;
  385. }
  386. if (!GetIEditor()->GetPluginManager()->CanAllPluginsExitNow())
  387. {
  388. return false;
  389. }
  390. return true;
  391. }
  392. bool CCryEditDoc::SaveModified()
  393. {
  394. if (!IsModified())
  395. {
  396. return true;
  397. }
  398. AzToolsFramework::Prefab::TemplateId rootPrefabTemplateId = m_prefabEditorEntityOwnershipInterface->GetRootPrefabTemplateId();
  399. if (!m_prefabSystemComponentInterface->AreDirtyTemplatesPresent(rootPrefabTemplateId))
  400. {
  401. return true;
  402. }
  403. int prefabSaveSelection = m_prefabIntegrationInterface->HandleRootPrefabClosure(rootPrefabTemplateId);
  404. // In order to get the accept and reject codes of QDialog and QDialogButtonBox aligned, we do (1-prefabSaveSelection) here.
  405. // For example, QDialog::Rejected(0) is emitted when dialog is closed. But the int value corresponds to
  406. // QDialogButtonBox::AcceptRole(0).
  407. switch (1 - prefabSaveSelection)
  408. {
  409. case QDialogButtonBox::AcceptRole:
  410. return true;
  411. case QDialogButtonBox::RejectRole:
  412. return false;
  413. case QDialogButtonBox::InvalidRole:
  414. SetModifiedFlag(false);
  415. return true;
  416. }
  417. Q_UNREACHABLE();
  418. }
  419. void CCryEditDoc::OnFileSaveAs()
  420. {
  421. CLevelFileDialog levelFileDialog(false);
  422. levelFileDialog.show();
  423. levelFileDialog.adjustSize();
  424. if (levelFileDialog.exec() == QDialog::Accepted)
  425. {
  426. if (OnSaveDocument(levelFileDialog.GetFileName()))
  427. {
  428. CCryEditApp::instance()->AddToRecentFileList(levelFileDialog.GetFileName());
  429. AzToolsFramework::Prefab::TemplateId rootPrefabTemplateId =
  430. m_prefabEditorEntityOwnershipInterface->GetRootPrefabTemplateId();
  431. SetModifiedFlag(m_prefabSystemComponentInterface->AreDirtyTemplatesPresent(rootPrefabTemplateId));
  432. }
  433. }
  434. }
  435. bool CCryEditDoc::OnOpenDocument(const QString& lpszPathName)
  436. {
  437. TOpenDocContext context;
  438. if (!BeforeOpenDocument(lpszPathName, context))
  439. {
  440. return false;
  441. }
  442. return DoOpenDocument(context);
  443. }
  444. bool CCryEditDoc::BeforeOpenDocument(const QString& lpszPathName, TOpenDocContext& context)
  445. {
  446. const AZ::TimeMs timeMs = AZ::GetRealElapsedTimeMs();
  447. const double timeSec = AZ::TimeMsToSecondsDouble(timeMs);
  448. const CTimeValue loading_start_time(timeSec);
  449. // restore directory to root.
  450. QDir::setCurrent(GetIEditor()->GetPrimaryCDFolder());
  451. QString absolutePath = lpszPathName;
  452. QFileInfo fileInfo(absolutePath);
  453. QString friendlyDisplayName = Path::GetRelativePath(absolutePath, true);
  454. CLogFile::FormatLine("Opening level %s", friendlyDisplayName.toUtf8().data());
  455. // normalize the file path.
  456. absolutePath = Path::ToUnixPath(QFileInfo(absolutePath).canonicalFilePath());
  457. context.loading_start_time = loading_start_time;
  458. context.absoluteLevelPath = absolutePath;
  459. return true;
  460. }
  461. bool CCryEditDoc::DoOpenDocument(TOpenDocContext& context)
  462. {
  463. const CTimeValue& loading_start_time = context.loading_start_time;
  464. // normalize the path so that its the same in all following calls:
  465. QString levelFilePath = QFileInfo(context.absoluteLevelPath).absoluteFilePath();
  466. context.absoluteLevelPath = levelFilePath;
  467. m_bLoadFailed = false;
  468. QString levelFolderAbsolutePath = QFileInfo(context.absoluteLevelPath).absolutePath();
  469. TDocMultiArchive arrXmlAr = {};
  470. if (!LoadLevel(arrXmlAr, context.absoluteLevelPath))
  471. {
  472. m_bLoadFailed = true;
  473. }
  474. ReleaseXmlArchiveArray(arrXmlAr);
  475. if (m_bLoadFailed)
  476. {
  477. return false;
  478. }
  479. // Load AZ entities for the editor.
  480. if (!LoadEntitiesFromLevel(context.absoluteLevelPath))
  481. {
  482. m_bLoadFailed = true;
  483. }
  484. if (m_bLoadFailed)
  485. {
  486. return false;
  487. }
  488. StartStreamingLoad();
  489. const AZ::TimeMs timeMs = AZ::GetRealElapsedTimeMs();
  490. const double timeSec = AZ::TimeMsToSecondsDouble(timeMs);
  491. const CTimeValue loading_end_time(timeSec);
  492. CLogFile::FormatLine("-----------------------------------------------------------");
  493. CLogFile::FormatLine("Successfully opened document %s", context.absoluteLevelPath.toUtf8().data());
  494. CLogFile::FormatLine("Level loading time: %.2f seconds", (loading_end_time - loading_start_time).GetSeconds());
  495. CLogFile::FormatLine("-----------------------------------------------------------");
  496. // It assumes loaded levels have already been exported. Can be a big fat lie, though.
  497. // The right way would require us to save to the level folder the export status of the
  498. // level.
  499. SetLevelExported(true);
  500. return true;
  501. }
  502. bool CCryEditDoc::OnNewDocument()
  503. {
  504. DeleteContents();
  505. m_pathName.clear();
  506. SetModifiedFlag(false);
  507. return true;
  508. }
  509. bool CCryEditDoc::OnSaveDocument(const QString& lpszPathName)
  510. {
  511. bool saveSuccess = false;
  512. bool shouldSaveLevel = true;
  513. if (gEnv->IsEditorSimulationMode())
  514. {
  515. // Don't allow saving in AI/Physics mode.
  516. // Prompt the user to exit Simulation Mode (aka AI/Phyics mode) before saving.
  517. QWidget* mainWindow = nullptr;
  518. AzToolsFramework::EditorRequests::Bus::BroadcastResult(mainWindow, &AzToolsFramework::EditorRequests::Bus::Events::GetMainWindow);
  519. QMessageBox msgBox(mainWindow);
  520. msgBox.setText(tr("You must exit AI/Physics mode before saving."));
  521. msgBox.setInformativeText(tr("The level will not be saved."));
  522. msgBox.setIcon(QMessageBox::Warning);
  523. msgBox.exec();
  524. }
  525. else
  526. {
  527. if (m_hasErrors || m_bLoadFailed)
  528. {
  529. QWidget* mainWindow = nullptr;
  530. AzToolsFramework::EditorRequests::Bus::BroadcastResult(
  531. mainWindow,
  532. &AzToolsFramework::EditorRequests::Bus::Events::GetMainWindow);
  533. // Prompt the user that saving may result in data loss. Most of the time this is not desired
  534. // (which is why 'cancel' is the default interaction), but this does provide users a way to still
  535. // save their level if this is the only way they can solve the erroneous data.
  536. QMessageBox msgBox(mainWindow);
  537. msgBox.setText(tr("Your level loaded with errors, you may lose work if you save."));
  538. msgBox.setInformativeText(tr("Do you want to save your changes?"));
  539. msgBox.setIcon(QMessageBox::Warning);
  540. msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Cancel);
  541. msgBox.setDefaultButton(QMessageBox::Cancel);
  542. int result = msgBox.exec();
  543. switch (result)
  544. {
  545. case QMessageBox::Save:
  546. // The user wishes to save, so don't bail.
  547. break;
  548. case QMessageBox::Cancel:
  549. // The user is canceling the save operation, so stop any saving from occuring.
  550. shouldSaveLevel = false;
  551. break;
  552. }
  553. }
  554. TSaveDocContext context;
  555. if (shouldSaveLevel && BeforeSaveDocument(lpszPathName, context))
  556. {
  557. DoSaveDocument(lpszPathName, context);
  558. saveSuccess = AfterSaveDocument(lpszPathName, context);
  559. }
  560. }
  561. return saveSuccess;
  562. }
  563. bool CCryEditDoc::BeforeSaveDocument(const QString& lpszPathName, TSaveDocContext& context)
  564. {
  565. // Restore directory to root.
  566. QDir::setCurrent(GetIEditor()->GetPrimaryCDFolder());
  567. // If we do not have a level loaded, we will also have an empty path, and that will
  568. // cause problems later in the save process. Early out here if that's the case
  569. QString levelFriendlyName = QFileInfo(lpszPathName).fileName();
  570. if (levelFriendlyName.isEmpty())
  571. {
  572. return false;
  573. }
  574. CryLog("Saving to %s...", levelFriendlyName.toUtf8().data());
  575. GetIEditor()->Notify(eNotify_OnBeginSceneSave);
  576. bool bSaved(true);
  577. context.bSaved = bSaved;
  578. return true;
  579. }
  580. bool CCryEditDoc::DoSaveDocument(const QString& filename, TSaveDocContext& context)
  581. {
  582. bool& bSaved = context.bSaved;
  583. if (!bSaved)
  584. {
  585. return false;
  586. }
  587. // Paranoia - we shouldn't get this far into the save routine without a level loaded (empty levelPath)
  588. // If nothing is loaded, we don't need to save anything
  589. if (filename.isEmpty())
  590. {
  591. bSaved = false;
  592. return false;
  593. }
  594. QString normalizedPath = Path::ToUnixPath(filename);
  595. bSaved = SaveLevel(normalizedPath);
  596. // Changes filename for this document.
  597. SetPathName(normalizedPath);
  598. return bSaved;
  599. }
  600. bool CCryEditDoc::AfterSaveDocument([[maybe_unused]] const QString& lpszPathName, TSaveDocContext& context, bool bShowPrompt)
  601. {
  602. bool bSaved = context.bSaved;
  603. GetIEditor()->Notify(eNotify_OnEndSceneSave);
  604. if (!bSaved)
  605. {
  606. if (bShowPrompt)
  607. {
  608. QMessageBox::warning(QApplication::activeWindow(), QString(), QObject::tr("Save Failed"), QMessageBox::Ok);
  609. }
  610. CLogFile::WriteLine("$4Document saving has failed.");
  611. }
  612. else
  613. {
  614. CLogFile::WriteLine("$3Document successfully saved");
  615. SetModifiedFlag(false);
  616. SetModifiedModules(eModifiedNothing);
  617. }
  618. return bSaved;
  619. }
  620. static bool TryRenameFile(const QString& oldPath, const QString& newPath, int retryAttempts=10)
  621. {
  622. QFile(newPath).setPermissions(QFile::ReadOther | QFile::WriteOther);
  623. QFile::remove(newPath);
  624. // try a few times, something can lock the file (such as virus scanner, etc).
  625. for (int attempts = 0; attempts < retryAttempts; ++attempts)
  626. {
  627. if (QFile::rename(oldPath, newPath))
  628. {
  629. return true;
  630. }
  631. AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(100));
  632. }
  633. return false;
  634. }
  635. bool CCryEditDoc::SaveLevel(const QString& filename)
  636. {
  637. AZ_PROFILE_FUNCTION(Editor);
  638. QWaitCursor wait;
  639. CAutoCheckOutDialogEnableForAll enableForAll;
  640. QString fullPathName = Path::ToUnixPath(filename);
  641. QString originaLevelFilename = Path::GetFile(m_pathName);
  642. if (QFileInfo(filename).isRelative())
  643. {
  644. // Resolving the path through resolvepath would normalize and lowcase it, and in this case, we don't want that.
  645. fullPathName = Path::ToUnixPath(QDir(QString::fromUtf8(gEnv->pFileIO->GetAlias("@projectroot@"))).absoluteFilePath(fullPathName));
  646. }
  647. if (!CFileUtil::OverwriteFile(fullPathName))
  648. {
  649. return false;
  650. }
  651. {
  652. AZ_PROFILE_SCOPE(Editor, "CCryEditDoc::SaveLevel BackupBeforeSave");
  653. BackupBeforeSave();
  654. }
  655. // need to copy existing level data before saving to different folder
  656. const QString oldLevelFolder = Path::GetPath(GetLevelPathName()); // get just the folder name
  657. QString newLevelFolder = Path::GetPath(fullPathName);
  658. CFileUtil::CreateDirectory(newLevelFolder.toUtf8().data());
  659. GetIEditor()->GetGameEngine()->SetLevelPath(newLevelFolder);
  660. // QFileInfo operator== takes care of many side cases and will return true
  661. // if the folder is the same folder, even if other things (like slashes, etc) are wrong
  662. if (QFileInfo(oldLevelFolder) != QFileInfo(newLevelFolder))
  663. {
  664. // if we're saving to a new folder, we need to copy the old folder tree.
  665. auto pIPak = GetIEditor()->GetSystem()->GetIPak();
  666. const QString oldLevelPattern = QDir(oldLevelFolder).absoluteFilePath("*.*");
  667. const QString oldLevelName = Path::GetFile(GetLevelPathName());
  668. const QString oldLevelXml = Path::ReplaceExtension(oldLevelName, "xml");
  669. AZ::IO::ArchiveFileIterator findHandle = pIPak->FindFirst(oldLevelPattern.toUtf8().data(), AZ::IO::FileSearchLocation::Any);
  670. if (findHandle)
  671. {
  672. do
  673. {
  674. const QString sourceName{ QString::fromUtf8(findHandle.m_filename.data(), aznumeric_cast<int>(findHandle.m_filename.size())) };
  675. if ((findHandle.m_fileDesc.nAttrib & AZ::IO::FileDesc::Attribute::Subdirectory) == AZ::IO::FileDesc::Attribute::Subdirectory)
  676. {
  677. // we only end up here if sourceName is a folder name.
  678. bool skipDir = sourceName == "." || sourceName == "..";
  679. skipDir |= IsBackupOrTempLevelSubdirectory(sourceName);
  680. skipDir |= sourceName == "Layers"; // layers folder will be created and written out as part of saving
  681. if (!skipDir)
  682. {
  683. QString oldFolderName = QDir(oldLevelFolder).absoluteFilePath(sourceName);
  684. QString newFolderName = QDir(newLevelFolder).absoluteFilePath(sourceName);
  685. CFileUtil::CreateDirectory(newFolderName.toUtf8().data());
  686. CFileUtil::CopyTree(oldFolderName, newFolderName);
  687. }
  688. continue;
  689. }
  690. bool skipFile = sourceName.endsWith(".cry", Qt::CaseInsensitive) ||
  691. sourceName.endsWith(".ly", Qt::CaseInsensitive) ||
  692. sourceName == originaLevelFilename; // level file will be written out by saving, ignore the source one
  693. if (skipFile)
  694. {
  695. continue;
  696. }
  697. // close any paks in the source folder so that when the paks are re-opened there is
  698. // no stale cached metadata in the pak system
  699. if (sourceName.endsWith(".pak", Qt::CaseInsensitive))
  700. {
  701. QString oldPackName = QDir(oldLevelFolder).absoluteFilePath(sourceName);
  702. pIPak->ClosePack(oldPackName.toUtf8().constData());
  703. }
  704. QString destName = sourceName;
  705. // copy oldLevel.xml -> newLevel.xml
  706. if (sourceName.compare(oldLevelXml, Qt::CaseInsensitive) == 0)
  707. {
  708. destName = Path::ReplaceExtension(Path::GetFile(fullPathName), "xml");
  709. }
  710. QString oldFilePath = QDir(oldLevelFolder).absoluteFilePath(sourceName);
  711. QString newFilePath = QDir(newLevelFolder).absoluteFilePath(destName);
  712. CFileUtil::CopyFile(oldFilePath, newFilePath);
  713. } while ((findHandle = pIPak->FindNext(findHandle)));
  714. pIPak->FindClose(findHandle);
  715. }
  716. // ensure that copied files are not read-only
  717. CFileUtil::ForEach(newLevelFolder, [](const QString& filePath)
  718. {
  719. QFile(filePath).setPermissions(QFile::ReadOther | QFile::WriteOther);
  720. });
  721. }
  722. AfterSave();
  723. // temp files (to be ignored by AssetProcessor take the form $tmp[0-9]*_...). we will conform
  724. // to that to make this file invisible to AP until it has been written completely.
  725. QString tempSaveFile = QDir(newLevelFolder).absoluteFilePath("$tmp_levelSave.tmp");
  726. QFile(tempSaveFile).setPermissions(QFile::ReadOther | QFile::WriteOther);
  727. QFile::remove(tempSaveFile);
  728. // Save AZ entities to the editor level.
  729. bool contentsAllSaved = false; // abort level save if anything within it fails
  730. auto tempFilenameStrData = tempSaveFile.toStdString();
  731. auto filenameStrData = fullPathName.toStdString();
  732. if (m_prefabEditorEntityOwnershipInterface)
  733. {
  734. AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
  735. AZ_Assert(fileIO, "No File IO implementation available");
  736. AZ::IO::HandleType tempSaveFileHandle;
  737. AZ::IO::Result openResult = fileIO->Open(tempFilenameStrData.c_str(), AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeBinary, tempSaveFileHandle);
  738. contentsAllSaved = openResult;
  739. if (openResult)
  740. {
  741. AZ::IO::FileIOStream stream(tempSaveFileHandle, AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeBinary, false);
  742. contentsAllSaved = m_prefabEditorEntityOwnershipInterface->SaveToStream(stream, AZStd::string_view(filenameStrData.data(), filenameStrData.size()));
  743. stream.Close();
  744. }
  745. }
  746. if (!contentsAllSaved)
  747. {
  748. AZ_Error("Editor", false, "Error when writing level '%s' into tmpfile '%s'", filenameStrData.c_str(), tempFilenameStrData.c_str());
  749. QFile::remove(tempSaveFile);
  750. return false;
  751. }
  752. if (!TryRenameFile(tempSaveFile, fullPathName))
  753. {
  754. gEnv->pLog->LogWarning("Unable to move file %s to %s when saving", tempSaveFile.toUtf8().data(), fullPathName.toUtf8().data());
  755. return false;
  756. }
  757. // Commit changes to the disk.
  758. _flushall();
  759. AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast(&AzToolsFramework::ToolsApplicationEvents::OnSaveLevel);
  760. return true;
  761. }
  762. bool CCryEditDoc::LoadEntitiesFromLevel(const QString& levelPakFile)
  763. {
  764. bool loadedSuccessfully = false;
  765. AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
  766. AZ_Assert(fileIO, "No File IO implementation available");
  767. AZ::IO::HandleType fileHandle;
  768. AZ::IO::Result openResult = fileIO->Open(levelPakFile.toUtf8().data(), AZ::IO::OpenMode::ModeRead | AZ::IO::OpenMode::ModeBinary, fileHandle);
  769. if (openResult)
  770. {
  771. AZ::IO::FileIOStream stream(fileHandle, AZ::IO::OpenMode::ModeRead | AZ::IO::OpenMode::ModeBinary, false);
  772. AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult(
  773. loadedSuccessfully, &AzToolsFramework::EditorEntityContextRequests::LoadFromStreamWithLayers, stream, levelPakFile);
  774. stream.Close();
  775. }
  776. return loadedSuccessfully;
  777. }
  778. bool CCryEditDoc::LoadLevel(TDocMultiArchive& arrXmlAr, const QString& absoluteCryFilePath)
  779. {
  780. QString folderPath = QFileInfo(absoluteCryFilePath).absolutePath();
  781. OnStartLevelResourceList();
  782. GetIEditor()->Notify(eNotify_OnBeginLoad);
  783. CrySystemEventBus::Broadcast(&CrySystemEventBus::Events::OnCryEditorBeginLoad);
  784. //GetISystem()->GetISystemEventDispatcher()->OnSystemEvent( ESYSTEM_EVENT_LEVEL_LOAD_START,0,0 );
  785. DeleteContents();
  786. // Set level path directly *after* DeleteContents(), since that will unload the previous level and clear the level path.
  787. GetIEditor()->GetGameEngine()->SetLevelPath(folderPath);
  788. SetModifiedFlag(true); // dirty during de-serialize
  789. SetModifiedModules(eModifiedAll);
  790. Load(arrXmlAr, absoluteCryFilePath);
  791. GetISystem()->GetISystemEventDispatcher()->OnSystemEvent(ESYSTEM_EVENT_LEVEL_LOAD_END, 0, 0);
  792. SetModifiedFlag(false); // start off with unmodified
  793. SetModifiedModules(eModifiedNothing);
  794. SetDocumentReady(true);
  795. GetIEditor()->Notify(eNotify_OnEndLoad);
  796. CrySystemEventBus::Broadcast(&CrySystemEventBus::Events::OnCryEditorEndLoad);
  797. GetIEditor()->SetStatusText("Ready");
  798. return true;
  799. }
  800. void CCryEditDoc::Hold(const QString& holdName)
  801. {
  802. Hold(holdName, holdName);
  803. }
  804. void CCryEditDoc::Hold(const QString& holdName, const QString& relativeHoldPath)
  805. {
  806. if (!IsDocumentReady())
  807. {
  808. return;
  809. }
  810. QString levelPath = GetIEditor()->GetGameEngine()->GetLevelPath();
  811. char resolvedLevelPath[AZ_MAX_PATH_LEN] = { 0 };
  812. AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(levelPath.toUtf8().data(), resolvedLevelPath, AZ_MAX_PATH_LEN);
  813. QString holdPath = QString::fromUtf8(resolvedLevelPath) + "/" + relativeHoldPath + "/";
  814. QString holdFilename = holdPath + holdName + GetIEditor()->GetGameEngine()->GetLevelExtension();
  815. // never auto-backup while we're trying to hold.
  816. bool oldBackup = gSettings.bBackupOnSave;
  817. gSettings.bBackupOnSave = false;
  818. SaveLevel(holdFilename);
  819. gSettings.bBackupOnSave = oldBackup;
  820. GetIEditor()->GetGameEngine()->SetLevelPath(levelPath);
  821. }
  822. void CCryEditDoc::Fetch(const QString& relativeHoldPath, bool bShowMessages, bool bDelHoldFolder)
  823. {
  824. Fetch(relativeHoldPath, relativeHoldPath, bShowMessages, bDelHoldFolder ? FetchPolicy::DELETE_FOLDER : FetchPolicy::PRESERVE);
  825. }
  826. void CCryEditDoc::Fetch(const QString& holdName, const QString& relativeHoldPath, bool bShowMessages, FetchPolicy policy)
  827. {
  828. if (!IsDocumentReady())
  829. {
  830. return;
  831. }
  832. QString levelPath = GetIEditor()->GetGameEngine()->GetLevelPath();
  833. char resolvedLevelPath[AZ_MAX_PATH_LEN] = { 0 };
  834. AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(levelPath.toUtf8().data(), resolvedLevelPath, AZ_MAX_PATH_LEN);
  835. QString holdPath = QString::fromUtf8(resolvedLevelPath) + "/" + relativeHoldPath + "/";
  836. QString holdFilename = holdPath + holdName + GetIEditor()->GetGameEngine()->GetLevelExtension();
  837. {
  838. QFile cFile(holdFilename);
  839. // Open the file for writing, create it if needed
  840. if (!cFile.open(QFile::ReadOnly))
  841. {
  842. if (bShowMessages)
  843. {
  844. QMessageBox::information(QApplication::activeWindow(), QString(), QObject::tr("You have to use 'Hold' before you can fetch!"));
  845. }
  846. return;
  847. }
  848. }
  849. // Does the document contain unsaved data ?
  850. if (bShowMessages && IsModified() &&
  851. 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)
  852. {
  853. return;
  854. }
  855. GetIEditor()->FlushUndo();
  856. TDocMultiArchive arrXmlAr = {};
  857. if (!LoadXmlArchiveArray(arrXmlAr, holdFilename, holdPath))
  858. {
  859. 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);
  860. AZ_Error("EditDoc", false, "Fetch failed to load the Xml Archive");
  861. return;
  862. }
  863. // Load the state
  864. LoadLevel(arrXmlAr, holdFilename);
  865. // Load AZ entities for the editor.
  866. LoadEntitiesFromLevel(holdFilename);
  867. GetIEditor()->GetGameEngine()->SetLevelPath(levelPath);
  868. GetIEditor()->FlushUndo();
  869. switch (policy)
  870. {
  871. case FetchPolicy::DELETE_FOLDER:
  872. CFileUtil::Deltree(holdPath.toUtf8().data(), true);
  873. break;
  874. case FetchPolicy::DELETE_LY_FILE:
  875. CFileUtil::DeleteFile(holdFilename);
  876. break;
  877. default:
  878. break;
  879. }
  880. }
  881. //////////////////////////////////////////////////////////////////////////
  882. namespace {
  883. struct SFolderTime
  884. {
  885. QString folder;
  886. time_t creationTime;
  887. };
  888. bool SortByCreationTime(SFolderTime& a, SFolderTime& b)
  889. {
  890. return a.creationTime < b.creationTime;
  891. }
  892. // This function, given a source folder to scan, returns all folders within that folder
  893. // non-recursively. They will be sorted by time, with most recent first, and oldest last.
  894. void CollectAllFoldersByTime(const char* sourceFolder, std::vector<SFolderTime>& outputFolders)
  895. {
  896. QString folderMask(sourceFolder);
  897. AZ::IO::ArchiveFileIterator handle = gEnv->pCryPak->FindFirst((folderMask + "/*").toUtf8().data());
  898. if (handle)
  899. {
  900. do
  901. {
  902. if (handle.m_filename.front() == '.')
  903. {
  904. continue;
  905. }
  906. if ((handle.m_fileDesc.nAttrib & AZ::IO::FileDesc::Attribute::Subdirectory) == AZ::IO::FileDesc::Attribute::Subdirectory)
  907. {
  908. SFolderTime ft;
  909. ft.folder = QString::fromUtf8(handle.m_filename.data(), aznumeric_cast<int>(handle.m_filename.size()));
  910. ft.creationTime = handle.m_fileDesc.tCreate;
  911. outputFolders.push_back(ft);
  912. }
  913. } while (handle = gEnv->pCryPak->FindNext(handle));
  914. gEnv->pCryPak->FindClose(handle);
  915. }
  916. std::sort(outputFolders.begin(), outputFolders.end(), SortByCreationTime);
  917. }
  918. }
  919. bool CCryEditDoc::BackupBeforeSave(bool force)
  920. {
  921. // This function will copy the contents of an entire level folder to a backup folder
  922. // and delete older ones based on user preferences.
  923. if (!force && !gSettings.bBackupOnSave)
  924. {
  925. return true; // not an error
  926. }
  927. QString levelPath = GetIEditor()->GetGameEngine()->GetLevelPath();
  928. if (levelPath.isEmpty())
  929. {
  930. return false;
  931. }
  932. char resolvedLevelPath[AZ_MAX_PATH_LEN] = { 0 };
  933. AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(levelPath.toUtf8().data(), resolvedLevelPath, AZ_MAX_PATH_LEN);
  934. QWaitCursor wait;
  935. QString saveBackupPath = QString::fromUtf8(resolvedLevelPath) + "/" + kSaveBackupFolder;
  936. std::vector<SFolderTime> folders;
  937. CollectAllFoldersByTime(saveBackupPath.toUtf8().data(), folders);
  938. for (int i = int(folders.size()) - gSettings.backupOnSaveMaxCount; i >= 0; --i)
  939. {
  940. CFileUtil::Deltree(QStringLiteral("%1/%2/").arg(saveBackupPath, folders[i].folder).toUtf8().data(), true);
  941. }
  942. QDateTime theTime = QDateTime::currentDateTime();
  943. QString subFolder = theTime.toString("yyyy-MM-dd [HH.mm.ss]");
  944. QString levelName = GetIEditor()->GetGameEngine()->GetLevelName();
  945. QString backupPath = saveBackupPath + "/" + subFolder;
  946. AZ::IO::FileIOBase::GetDirectInstance()->CreatePath(backupPath.toUtf8().data());
  947. QString sourcePath = QString::fromUtf8(resolvedLevelPath) + "/";
  948. QString ignoredFiles;
  949. for (const char* backupOrTempFolderName : kBackupOrTempFolders)
  950. {
  951. if (!ignoredFiles.isEmpty())
  952. {
  953. ignoredFiles += "|";
  954. }
  955. ignoredFiles += QString::fromUtf8(backupOrTempFolderName);
  956. }
  957. // copy that whole tree:
  958. AZ_TracePrintf("Editor", "Saving level backup to '%s'...\n", backupPath.toUtf8().data());
  959. if (IFileUtil::ETREECOPYOK != CFileUtil::CopyTree(sourcePath, backupPath, true, false, ignoredFiles.toUtf8().data()))
  960. {
  961. gEnv->pLog->LogWarning("Attempting to save backup to %s before saving, but could not write all files.", backupPath.toUtf8().data());
  962. return false;
  963. }
  964. return true;
  965. }
  966. void CCryEditDoc::SaveAutoBackup(bool bForce)
  967. {
  968. if (!bForce && (!gSettings.autoBackupEnabled || GetIEditor()->IsInGameMode()))
  969. {
  970. return;
  971. }
  972. QString levelPath = GetIEditor()->GetGameEngine()->GetLevelPath();
  973. if (levelPath.isEmpty())
  974. {
  975. return;
  976. }
  977. static bool isInProgress = false;
  978. if (isInProgress)
  979. {
  980. return;
  981. }
  982. isInProgress = true;
  983. QWaitCursor wait;
  984. QString autoBackupPath = levelPath + "/" + kAutoBackupFolder;
  985. // collect all subfolders
  986. std::vector<SFolderTime> folders;
  987. CollectAllFoldersByTime(autoBackupPath.toUtf8().data(), folders);
  988. for (int i = int(folders.size()) - gSettings.autoBackupMaxCount; i >= 0; --i)
  989. {
  990. CFileUtil::Deltree(QStringLiteral("%1/%2/").arg(autoBackupPath, folders[i].folder).toUtf8().data(), true);
  991. }
  992. // save new backup
  993. QDateTime theTime = QDateTime::currentDateTime();
  994. QString subFolder = theTime.toString(QStringLiteral("yyyy-MM-dd [HH.mm.ss]"));
  995. QString levelName = GetIEditor()->GetGameEngine()->GetLevelName();
  996. QString filename = autoBackupPath + "/" + subFolder + "/" + levelName + "/" + levelName + GetIEditor()->GetGameEngine()->GetLevelExtension();
  997. SaveLevel(filename);
  998. GetIEditor()->GetGameEngine()->SetLevelPath(levelPath);
  999. isInProgress = false;
  1000. }
  1001. bool CCryEditDoc::IsLevelExported() const
  1002. {
  1003. return m_boLevelExported;
  1004. }
  1005. void CCryEditDoc::SetLevelExported(bool boExported)
  1006. {
  1007. m_boLevelExported = boExported;
  1008. }
  1009. void CCryEditDoc::RegisterListener(IDocListener* listener)
  1010. {
  1011. if (listener == nullptr)
  1012. {
  1013. return;
  1014. }
  1015. if (std::find(m_listeners.begin(), m_listeners.end(), listener) == m_listeners.end())
  1016. {
  1017. m_listeners.push_back(listener);
  1018. }
  1019. }
  1020. void CCryEditDoc::UnregisterListener(IDocListener* listener)
  1021. {
  1022. m_listeners.remove(listener);
  1023. }
  1024. void CCryEditDoc::LogLoadTime(int time) const
  1025. {
  1026. QString appFilePath = QDir::toNativeSeparators(QCoreApplication::applicationFilePath());
  1027. QString exePath = Path::GetPath(appFilePath);
  1028. QString filename = Path::Make(exePath, "LevelLoadTime.log");
  1029. QString level = GetIEditor()->GetGameEngine()->GetLevelPath();
  1030. CLogFile::FormatLine("[LevelLoadTime] Level %s loaded in %d seconds", level.toUtf8().data(), time / 1000);
  1031. #if defined(AZ_PLATFORM_WINDOWS)
  1032. SetFileAttributesW(filename.toStdWString().c_str(), FILE_ATTRIBUTE_ARCHIVE);
  1033. #endif
  1034. QFile file(filename);
  1035. if (!file.open(QFile::Append | QFile::Text))
  1036. {
  1037. return;
  1038. }
  1039. char version[50];
  1040. GetIEditor()->GetFileVersion().ToShortString(version, AZ_ARRAY_SIZE(version));
  1041. time = time / 1000;
  1042. QString text = QStringLiteral("\n[%1] Level %2 loaded in %3 seconds").arg(version, level).arg(time);
  1043. file.write(text.toUtf8());
  1044. }
  1045. void CCryEditDoc::SetDocumentReady(bool bReady)
  1046. {
  1047. m_bDocumentReady = bReady;
  1048. }
  1049. void CCryEditDoc::OnStartLevelResourceList()
  1050. {
  1051. // after loading another level we clear the RFOM_Level list, the first time the list should be empty
  1052. static bool bFirstTime = true;
  1053. if (bFirstTime)
  1054. {
  1055. const char* pResFilename = gEnv->pCryPak->GetResourceList(AZ::IO::IArchive::RFOM_Level)->GetFirst();
  1056. while (pResFilename)
  1057. {
  1058. // This should be fixed because ExecuteCommandLine is executed right after engine init as we assume the
  1059. // engine already has all data loaded an is initialized to process commands. Loading data afterwards means
  1060. // some init was done later which can cause problems when running in the engine batch mode (executing console commands).
  1061. gEnv->pLog->LogError("'%s' was loaded after engine init but before level load/new (should be fixed)", pResFilename);
  1062. pResFilename = gEnv->pCryPak->GetResourceList(AZ::IO::IArchive::RFOM_Level)->GetNext();
  1063. }
  1064. bFirstTime = false;
  1065. }
  1066. gEnv->pCryPak->GetResourceList(AZ::IO::IArchive::RFOM_Level)->Clear();
  1067. }
  1068. bool CCryEditDoc::DoFileSave()
  1069. {
  1070. // If the file to save is the temporary level it should 'save as' since temporary levels will get deleted
  1071. const char* temporaryLevelName = GetTemporaryLevelName();
  1072. if (QString::compare(GetIEditor()->GetLevelName(), temporaryLevelName) == 0)
  1073. {
  1074. QString filename;
  1075. if (CCryEditApp::instance()->GetDocManager()->DoPromptFileName(filename, ID_FILE_SAVE_AS, 0, false, nullptr)
  1076. && !filename.isEmpty() && !QFileInfo(filename).exists())
  1077. {
  1078. if (SaveLevel(filename))
  1079. {
  1080. DeleteTemporaryLevel();
  1081. QString newLevelPath = filename.left(filename.lastIndexOf('/') + 1);
  1082. GetIEditor()->GetDocument()->SetPathName(filename);
  1083. GetIEditor()->GetGameEngine()->SetLevelPath(newLevelPath);
  1084. return true;
  1085. }
  1086. }
  1087. return false;
  1088. }
  1089. if (!IsDocumentReady())
  1090. {
  1091. return false;
  1092. }
  1093. return Internal::SaveLevel();
  1094. }
  1095. const char* CCryEditDoc::GetTemporaryLevelName() const
  1096. {
  1097. return gEnv->pConsole->GetCVar("g_TemporaryLevelName")->GetString();
  1098. }
  1099. void CCryEditDoc::DeleteTemporaryLevel()
  1100. {
  1101. QString tempLevelPath = (Path::GetEditingGameDataFolder() + "/Levels/" + GetTemporaryLevelName()).c_str();
  1102. GetIEditor()->GetSystem()->GetIPak()->ClosePacks(tempLevelPath.toUtf8().data());
  1103. CFileUtil::Deltree(tempLevelPath.toUtf8().data(), true);
  1104. }
  1105. void CCryEditDoc::InitEmptyLevel(int /*resolution*/, int /*unitSize*/, bool /*bUseTerrain*/)
  1106. {
  1107. GetIEditor()->SetStatusText("Initializing Level...");
  1108. OnStartLevelResourceList();
  1109. GetIEditor()->Notify(eNotify_OnBeginNewScene);
  1110. CLogFile::WriteLine("Preparing new document...");
  1111. //cleanup resources!
  1112. GetISystem()->GetISystemEventDispatcher()->OnSystemEvent(ESYSTEM_EVENT_LEVEL_POST_UNLOAD, 0, 0);
  1113. //////////////////////////////////////////////////////////////////////////
  1114. // Initialize defaults.
  1115. //////////////////////////////////////////////////////////////////////////
  1116. if (!GetIEditor()->IsInPreviewMode())
  1117. {
  1118. GetIEditor()->ReloadTemplates();
  1119. m_environmentTemplate = GetIEditor()->FindTemplate("Environment");
  1120. GetIEditor()->GetGameEngine()->SetLevelCreated(true);
  1121. GetIEditor()->GetGameEngine()->SetLevelCreated(false);
  1122. }
  1123. {
  1124. // Notify listeners.
  1125. std::list<IDocListener*> listeners = m_listeners;
  1126. for (IDocListener* listener : listeners)
  1127. {
  1128. listener->OnNewDocument();
  1129. }
  1130. }
  1131. // Tell the system that the level has been created/loaded.
  1132. GetISystem()->GetISystemEventDispatcher()->OnSystemEvent(ESYSTEM_EVENT_LEVEL_LOAD_END, 0, 0);
  1133. GetIEditor()->Notify(eNotify_OnEndNewScene);
  1134. SetModifiedFlag(false);
  1135. SetLevelExported(false);
  1136. SetModifiedModules(eModifiedNothing);
  1137. GetIEditor()->SetStatusText("Ready");
  1138. }
  1139. void CCryEditDoc::CreateDefaultLevelAssets([[maybe_unused]] int resolution, [[maybe_unused]] int unitSize)
  1140. {
  1141. AzToolsFramework::EditorLevelNotificationBus::Broadcast(&AzToolsFramework::EditorLevelNotificationBus::Events::OnNewLevelCreated);
  1142. }
  1143. void CCryEditDoc::OnEnvironmentPropertyChanged(IVariable* pVar)
  1144. {
  1145. if (pVar == nullptr)
  1146. {
  1147. return;
  1148. }
  1149. XmlNodeRef node = GetEnvironmentTemplate();
  1150. if (node == nullptr)
  1151. {
  1152. return;
  1153. }
  1154. // QVariant will not convert a void * to int, so do it manually.
  1155. int nKey = static_cast<int>(reinterpret_cast<intptr_t>(pVar->GetUserData().value<void*>()));
  1156. int nGroup = (nKey & 0xFFFF0000) >> 16;
  1157. int nChild = (nKey & 0x0000FFFF);
  1158. if (nGroup < 0 || nGroup >= node->getChildCount())
  1159. {
  1160. return;
  1161. }
  1162. XmlNodeRef groupNode = node->getChild(nGroup);
  1163. if (groupNode == nullptr)
  1164. {
  1165. return;
  1166. }
  1167. if (nChild < 0 || nChild >= groupNode->getChildCount())
  1168. {
  1169. return;
  1170. }
  1171. XmlNodeRef childNode = groupNode->getChild(nChild);
  1172. if (childNode == nullptr)
  1173. {
  1174. return;
  1175. }
  1176. QString childValue;
  1177. if (pVar->GetDataType() == IVariable::DT_COLOR)
  1178. {
  1179. Vec3 value;
  1180. pVar->Get(value);
  1181. QColor gammaColor = ColorLinearToGamma(ColorF(value.x, value.y, value.z));
  1182. childValue = QStringLiteral("%1,%2,%3").arg(gammaColor.red()).arg(gammaColor.green()).arg(gammaColor.blue());
  1183. }
  1184. else
  1185. {
  1186. pVar->Get(childValue);
  1187. }
  1188. childNode->setAttr("value", childValue.toUtf8().data());
  1189. }
  1190. QString CCryEditDoc::GetCryIndexPath(const char* levelFilePath) const
  1191. {
  1192. QString levelPath = Path::GetPath(levelFilePath);
  1193. QString levelName = Path::GetFileName(levelFilePath);
  1194. return Path::AddPathSlash(levelPath + levelName + "_editor");
  1195. }
  1196. bool CCryEditDoc::LoadXmlArchiveArray(TDocMultiArchive& arrXmlAr, const QString& absoluteLevelPath, const QString& levelPath)
  1197. {
  1198. auto pIPak = GetIEditor()->GetSystem()->GetIPak();
  1199. //if (m_pSWDoc->IsNull())
  1200. {
  1201. CXmlArchive* pXmlAr = new CXmlArchive();
  1202. if (!pXmlAr)
  1203. {
  1204. return false;
  1205. }
  1206. CXmlArchive& xmlAr = *pXmlAr;
  1207. xmlAr.bLoading = true;
  1208. // bound to the level folder, as if it were the assets folder.
  1209. // this mounts (whateverlevelname.ly) as @products@/Levels/whateverlevelname/ and thus it works...
  1210. bool openLevelPakFileSuccess = pIPak->OpenPack(levelPath.toUtf8().data(), absoluteLevelPath.toUtf8().data());
  1211. if (!openLevelPakFileSuccess)
  1212. {
  1213. return false;
  1214. }
  1215. CPakFile pakFile;
  1216. bool loadFromPakSuccess = xmlAr.LoadFromPak(levelPath, pakFile);
  1217. pIPak->ClosePack(absoluteLevelPath.toUtf8().data());
  1218. if (!loadFromPakSuccess)
  1219. {
  1220. return false;
  1221. }
  1222. FillXmlArArray(arrXmlAr, &xmlAr);
  1223. }
  1224. return true;
  1225. }
  1226. void CCryEditDoc::ReleaseXmlArchiveArray(TDocMultiArchive& arrXmlAr)
  1227. {
  1228. SAFE_DELETE(arrXmlAr[0]);
  1229. }
  1230. namespace AzToolsFramework
  1231. {
  1232. void CryEditDocFuncsHandler::Reflect(AZ::ReflectContext* context)
  1233. {
  1234. if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
  1235. {
  1236. // this will put these methods into the 'azlmbr.legacy.general' module
  1237. auto addLegacyGeneral = [](AZ::BehaviorContext::GlobalMethodBuilder methodBuilder)
  1238. {
  1239. methodBuilder->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
  1240. ->Attribute(AZ::Script::Attributes::Category, "Legacy/Editor")
  1241. ->Attribute(AZ::Script::Attributes::Module, "legacy.general");
  1242. };
  1243. addLegacyGeneral(behaviorContext->Method("save_level", ::Internal::SaveLevel, nullptr, "Saves the current level."));
  1244. }
  1245. }
  1246. }
  1247. #include <moc_CryEditDoc.cpp>