CryEditDoc.cpp 50 KB

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