CryEditDoc.cpp 50 KB

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