CryEditDoc.cpp 73 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166
  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/Slice/SliceUtilities.h>
  26. #include <AzToolsFramework/UI/UICore/WidgetHelpers.h>
  27. #include <AzToolsFramework/UI/Layer/NameConflictWarning.hxx>
  28. #include <AzToolsFramework/API/EditorLevelNotificationBus.h>
  29. // Editor
  30. #include "Settings.h"
  31. #include "PluginManager.h"
  32. #include "ViewManager.h"
  33. #include "DisplaySettings.h"
  34. #include "GameEngine.h"
  35. #include "CryEdit.h"
  36. #include "Include/IObjectManager.h"
  37. #include "ErrorReportDialog.h"
  38. #include "Util/AutoLogTime.h"
  39. #include "CheckOutDialog.h"
  40. #include "GameExporter.h"
  41. #include "MainWindow.h"
  42. #include "LevelFileDialog.h"
  43. #include "Undo/Undo.h"
  44. #include <Atom/RPI.Public/ViewportContext.h>
  45. #include <Atom/RPI.Public/ViewportContextBus.h>
  46. // LmbrCentral
  47. #include <LmbrCentral/Audio/AudioSystemComponentBus.h>
  48. static const char* kAutoBackupFolder = "_autobackup";
  49. static const char* kHoldFolder = "$tmp_hold"; // conform to the ignored file types $tmp[0-9]*_ regex
  50. static const char* kSaveBackupFolder = "_savebackup";
  51. static const char* kResizeTempFolder = "$tmp_resize"; // conform to the ignored file types $tmp[0-9]*_ regex
  52. static const char* kBackupOrTempFolders[] =
  53. {
  54. kAutoBackupFolder,
  55. kHoldFolder,
  56. kSaveBackupFolder,
  57. kResizeTempFolder,
  58. "_hold", // legacy name
  59. "_tmpresize", // legacy name
  60. };
  61. static const char* kLevelPathForSliceEditing = "EngineAssets/LevelForSliceEditing/LevelForSliceEditing.ly";
  62. static bool IsSliceFile(const QString& filePath)
  63. {
  64. return filePath.endsWith(AzToolsFramework::SliceUtilities::GetSliceFileExtension().c_str(), Qt::CaseInsensitive);
  65. }
  66. namespace Internal
  67. {
  68. bool SaveLevel()
  69. {
  70. if (!GetIEditor()->GetDocument()->DoSave(GetIEditor()->GetDocument()->GetActivePathName(), true))
  71. {
  72. return false;
  73. }
  74. return true;
  75. }
  76. }
  77. /////////////////////////////////////////////////////////////////////////////
  78. // CCryEditDoc construction/destruction
  79. CCryEditDoc::CCryEditDoc()
  80. : m_modifiedModuleFlags(eModifiedNothing)
  81. {
  82. ////////////////////////////////////////////////////////////////////////
  83. // Set member variables to initial values
  84. ////////////////////////////////////////////////////////////////////////
  85. m_fogTemplate = GetIEditor()->FindTemplate("Fog");
  86. m_environmentTemplate = GetIEditor()->FindTemplate("Environment");
  87. if (m_environmentTemplate)
  88. {
  89. m_fogTemplate = m_environmentTemplate->findChild("Fog");
  90. }
  91. else
  92. {
  93. m_environmentTemplate = XmlHelpers::CreateXmlNode("Environment");
  94. }
  95. GetIEditor()->SetDocument(this);
  96. CLogFile::WriteLine("Document created");
  97. bool isPrefabSystemEnabled = false;
  98. AzFramework::ApplicationRequests::Bus::BroadcastResult(isPrefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
  99. if (isPrefabSystemEnabled)
  100. {
  101. m_prefabSystemComponentInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabSystemComponentInterface>::Get();
  102. AZ_Assert(m_prefabSystemComponentInterface, "PrefabSystemComponentInterface is not found.");
  103. m_prefabEditorEntityOwnershipInterface = AZ::Interface<AzToolsFramework::PrefabEditorEntityOwnershipInterface>::Get();
  104. AZ_Assert(m_prefabEditorEntityOwnershipInterface, "PrefabEditorEntityOwnershipInterface is not found.");
  105. m_prefabLoaderInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabLoaderInterface>::Get();
  106. AZ_Assert(m_prefabLoaderInterface, "PrefabLoaderInterface is not found.");
  107. m_prefabIntegrationInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabIntegrationInterface>::Get();
  108. AZ_Assert(m_prefabIntegrationInterface, "PrefabIntegrationInterface is not found.");
  109. }
  110. }
  111. CCryEditDoc::~CCryEditDoc()
  112. {
  113. GetIEditor()->SetDocument(nullptr);
  114. CLogFile::WriteLine("Document destroyed");
  115. AzToolsFramework::SliceEditorEntityOwnershipServiceNotificationBus::Handler::BusDisconnect();
  116. }
  117. bool CCryEditDoc::IsModified() const
  118. {
  119. return m_modified;
  120. }
  121. void CCryEditDoc::SetModifiedFlag(bool modified)
  122. {
  123. m_modified = modified;
  124. }
  125. QString CCryEditDoc::GetLevelPathName() const
  126. {
  127. return m_pathName;
  128. }
  129. void CCryEditDoc::SetPathName(const QString& pathName)
  130. {
  131. if (IsSliceFile(pathName))
  132. {
  133. m_pathName = kLevelPathForSliceEditing;
  134. m_slicePathName = pathName;
  135. }
  136. else
  137. {
  138. m_pathName = pathName;
  139. m_slicePathName.clear();
  140. }
  141. SetTitle(pathName.isEmpty() ? tr("Untitled") : PathUtil::GetFileName(pathName.toUtf8().data()).c_str());
  142. }
  143. QString CCryEditDoc::GetSlicePathName() const
  144. {
  145. return m_slicePathName;
  146. }
  147. CCryEditDoc::DocumentEditingMode CCryEditDoc::GetEditMode() const
  148. {
  149. return m_slicePathName.isEmpty() ? CCryEditDoc::DocumentEditingMode::LevelEdit : CCryEditDoc::DocumentEditingMode::SliceEdit;
  150. }
  151. QString CCryEditDoc::GetActivePathName() const
  152. {
  153. return GetEditMode() == CCryEditDoc::DocumentEditingMode::SliceEdit ? GetSlicePathName() : GetLevelPathName();
  154. }
  155. QString CCryEditDoc::GetTitle() const
  156. {
  157. return m_title;
  158. }
  159. void CCryEditDoc::SetTitle(const QString& title)
  160. {
  161. m_title = title;
  162. }
  163. bool CCryEditDoc::IsBackupOrTempLevelSubdirectory(const QString& folderName)
  164. {
  165. for (const char* backupOrTempFolderName : kBackupOrTempFolders)
  166. {
  167. if (!folderName.compare(backupOrTempFolderName, Qt::CaseInsensitive))
  168. {
  169. return true;
  170. }
  171. }
  172. return false;
  173. }
  174. bool CCryEditDoc::DoSave(const QString& pathName, bool replace)
  175. {
  176. if (!OnSaveDocument(pathName.isEmpty() ? GetActivePathName() : pathName))
  177. {
  178. return false;
  179. }
  180. if (replace)
  181. {
  182. SetPathName(pathName);
  183. }
  184. return true;
  185. }
  186. bool CCryEditDoc::Save()
  187. {
  188. return OnSaveDocument(GetActivePathName());
  189. }
  190. void CCryEditDoc::DeleteContents()
  191. {
  192. m_hasErrors = false;
  193. SetDocumentReady(false);
  194. GetIEditor()->Notify(eNotify_OnCloseScene);
  195. CrySystemEventBus::Broadcast(&CrySystemEventBus::Events::OnCryEditorCloseScene);
  196. AzToolsFramework::EditorEntityContextRequestBus::Broadcast(
  197. &AzToolsFramework::EditorEntityContextRequestBus::Events::ResetEditorContext);
  198. //////////////////////////////////////////////////////////////////////////
  199. // Clear all undo info.
  200. //////////////////////////////////////////////////////////////////////////
  201. GetIEditor()->FlushUndo();
  202. // Notify listeners.
  203. for (IDocListener* listener : m_listeners)
  204. {
  205. listener->OnCloseDocument();
  206. }
  207. GetIEditor()->ResetViews();
  208. // Delete all objects from Object Manager.
  209. GetIEditor()->GetObjectManager()->DeleteAllObjects();
  210. // Load scripts data
  211. SetModifiedFlag(false);
  212. SetModifiedModules(eModifiedNothing);
  213. // Clear error reports if open.
  214. CErrorReportDialog::Clear();
  215. // Unload level specific audio binary data.
  216. LmbrCentral::AudioSystemComponentRequestBus::Broadcast(&LmbrCentral::AudioSystemComponentRequestBus::Events::LevelUnloadAudio);
  217. GetIEditor()->Notify(eNotify_OnSceneClosed);
  218. CrySystemEventBus::Broadcast(&CrySystemEventBus::Events::OnCryEditorSceneClosed);
  219. }
  220. void CCryEditDoc::Save(CXmlArchive& xmlAr)
  221. {
  222. TDocMultiArchive arrXmlAr;
  223. FillXmlArArray(arrXmlAr, &xmlAr);
  224. Save(arrXmlAr);
  225. }
  226. void CCryEditDoc::Save(TDocMultiArchive& arrXmlAr)
  227. {
  228. bool isPrefabEnabled = false;
  229. AzFramework::ApplicationRequests::Bus::BroadcastResult(isPrefabEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
  230. if (!isPrefabEnabled)
  231. {
  232. CAutoDocNotReady autoDocNotReady;
  233. if (arrXmlAr[DMAS_GENERAL] != nullptr)
  234. {
  235. (*arrXmlAr[DMAS_GENERAL]).root = XmlHelpers::CreateXmlNode("Level");
  236. (*arrXmlAr[DMAS_GENERAL]).root->setAttr("WaterColor", m_waterColor);
  237. char version[50];
  238. GetIEditor()->GetFileVersion().ToString(version, AZ_ARRAY_SIZE(version));
  239. (*arrXmlAr[DMAS_GENERAL]).root->setAttr("SandboxVersion", version);
  240. SerializeViewSettings((*arrXmlAr[DMAS_GENERAL]));
  241. // Fog settings ///////////////////////////////////////////////////////
  242. SerializeFogSettings((*arrXmlAr[DMAS_GENERAL]));
  243. }
  244. }
  245. AfterSave();
  246. }
  247. void CCryEditDoc::Load(CXmlArchive& xmlAr, const QString& szFilename)
  248. {
  249. TDocMultiArchive arrXmlAr;
  250. FillXmlArArray(arrXmlAr, &xmlAr);
  251. CCryEditDoc::Load(arrXmlAr, szFilename);
  252. }
  253. //////////////////////////////////////////////////////////////////////////
  254. void CCryEditDoc::Load(TDocMultiArchive& arrXmlAr, const QString& szFilename)
  255. {
  256. bool isPrefabEnabled = false;
  257. AzFramework::ApplicationRequests::Bus::BroadcastResult(isPrefabEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
  258. m_hasErrors = false;
  259. // Register a unique load event
  260. QString fileName = Path::GetFileName(szFilename);
  261. QString levelHash;
  262. if (!isPrefabEnabled)
  263. {
  264. levelHash = GetIEditor()->GetSettingsManager()->GenerateContentHash(arrXmlAr[DMAS_GENERAL]->root, fileName);
  265. }
  266. else
  267. {
  268. levelHash = szFilename;
  269. }
  270. SEventLog loadEvent("Level_" + Path::GetFileName(fileName), "", levelHash);
  271. // Register this level and its content hash as version
  272. GetIEditor()->GetSettingsManager()->AddToolVersion(fileName, levelHash);
  273. GetIEditor()->GetSettingsManager()->RegisterEvent(loadEvent);
  274. CAutoDocNotReady autoDocNotReady;
  275. HEAP_CHECK
  276. CLogFile::FormatLine("Loading from %s...", szFilename.toUtf8().data());
  277. QString szLevelPath = Path::GetPath(szFilename);
  278. {
  279. // Set game g_levelname variable to the name of current level.
  280. QString szGameLevelName = Path::GetFileName(szFilename);
  281. ICVar* sv_map = gEnv->pConsole->GetCVar("sv_map");
  282. if (sv_map)
  283. {
  284. sv_map->Set(szGameLevelName.toUtf8().data());
  285. }
  286. }
  287. // Starts recording the opening of files using the level category
  288. if (auto archive = AZ::Interface<AZ::IO::IArchive>::Get(); archive && archive->GetRecordFileOpenList() == AZ::IO::IArchive::RFOM_EngineStartup)
  289. {
  290. archive->RecordFileOpen(AZ::IO::IArchive::RFOM_Level);
  291. }
  292. GetIEditor()->Notify(eNotify_OnBeginSceneOpen);
  293. GetIEditor()->GetMovieSystem()->RemoveAllSequences();
  294. {
  295. // Start recording errors
  296. const ICVar* pShowErrorDialogOnLoad = gEnv->pConsole->GetCVar("ed_showErrorDialogOnLoad");
  297. CErrorsRecorder errorsRecorder(pShowErrorDialogOnLoad && (pShowErrorDialogOnLoad->GetIVal() != 0));
  298. bool usePrefabSystemForLevels = false;
  299. AzFramework::ApplicationRequests::Bus::BroadcastResult(
  300. usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
  301. if (!usePrefabSystemForLevels)
  302. {
  303. AZStd::string levelPakPath;
  304. if (AzFramework::StringFunc::Path::ConstructFull(szLevelPath.toUtf8().data(), "level", "pak", levelPakPath, true))
  305. {
  306. // Check whether level.pak is present
  307. if (!gEnv->pFileIO->Exists(levelPakPath.c_str()))
  308. {
  309. CryWarning(
  310. VALIDATOR_MODULE_EDITOR, VALIDATOR_WARNING,
  311. "level.pak is missing. This will cause other errors. To fix this, re-export the level.");
  312. }
  313. }
  314. }
  315. int t0 = GetTickCount();
  316. // Load level-specific audio data.
  317. AZStd::string levelFileName{ fileName.toUtf8().constData() };
  318. AZStd::to_lower(levelFileName.begin(), levelFileName.end());
  319. LmbrCentral::AudioSystemComponentRequestBus::Broadcast(
  320. &LmbrCentral::AudioSystemComponentRequestBus::Events::LevelLoadAudio, AZStd::string_view{ levelFileName });
  321. {
  322. CAutoLogTime logtime("Game Engine level load");
  323. GetIEditor()->GetGameEngine()->LoadLevel(true, true);
  324. }
  325. if (!isPrefabEnabled)
  326. {
  327. //////////////////////////////////////////////////////////////////////////
  328. // Load water color.
  329. //////////////////////////////////////////////////////////////////////////
  330. (*arrXmlAr[DMAS_GENERAL]).root->getAttr("WaterColor", m_waterColor);
  331. //////////////////////////////////////////////////////////////////////////
  332. // Load View Settings
  333. //////////////////////////////////////////////////////////////////////////
  334. SerializeViewSettings((*arrXmlAr[DMAS_GENERAL]));
  335. //////////////////////////////////////////////////////////////////////////
  336. // Fog settings
  337. //////////////////////////////////////////////////////////////////////////
  338. SerializeFogSettings((*arrXmlAr[DMAS_GENERAL]));
  339. }
  340. if (!isPrefabEnabled)
  341. {
  342. // Serialize Shader Cache.
  343. CAutoLogTime logtime("Load Level Shader Cache");
  344. }
  345. {
  346. // support old version of sequences
  347. IMovieSystem* pMs = GetIEditor()->GetMovieSystem();
  348. if (pMs)
  349. {
  350. for (int k = 0; k < pMs->GetNumSequences(); ++k)
  351. {
  352. IAnimSequence* seq = pMs->GetSequence(k);
  353. QString fullname = seq->GetName();
  354. CBaseObject* pObj = GetIEditor()->GetObjectManager()->FindObject(fullname);
  355. if (!pObj)
  356. {
  357. pObj = GetIEditor()->GetObjectManager()->NewObject("SequenceObject", nullptr, fullname);
  358. }
  359. }
  360. }
  361. }
  362. {
  363. CAutoLogTime logtime("Post Load");
  364. // Notify listeners.
  365. for (IDocListener* listener : m_listeners)
  366. {
  367. listener->OnLoadDocument();
  368. }
  369. }
  370. LogLoadTime(GetTickCount() - t0);
  371. // Loaded with success, remove event from log file
  372. GetIEditor()->GetSettingsManager()->UnregisterEvent(loadEvent);
  373. }
  374. GetIEditor()->Notify(eNotify_OnEndSceneOpen);
  375. }
  376. void CCryEditDoc::AfterSave()
  377. {
  378. // When saving level also save editor settings
  379. // Save settings
  380. gSettings.Save();
  381. GetIEditor()->GetDisplaySettings()->SaveRegistry();
  382. MainWindow::instance()->SaveConfig();
  383. }
  384. void CCryEditDoc::SerializeViewSettings(CXmlArchive& xmlAr)
  385. {
  386. // Load or restore the viewer settings from an XML
  387. if (xmlAr.bLoading)
  388. {
  389. bool useOldViewFormat = false;
  390. // Loading
  391. CLogFile::WriteLine("Loading View settings...");
  392. int numberOfGameViewports = GetIEditor()->GetViewManager()->GetNumberOfGameViewports();
  393. for (int i = 0; i < numberOfGameViewports; i++)
  394. {
  395. XmlNodeRef view;
  396. Vec3 vp(0.0f, 0.0f, 256.0f);
  397. Ang3 va(ZERO);
  398. auto viewName = QString("View%1").arg(i);
  399. view = xmlAr.root->findChild(viewName.toUtf8().constData());
  400. if (!view)
  401. {
  402. view = xmlAr.root->findChild("View");
  403. if (view)
  404. {
  405. useOldViewFormat = true;
  406. }
  407. }
  408. if (view)
  409. {
  410. auto viewerPosName = QString("ViewerPos%1").arg(useOldViewFormat ? "" : QString::number(i));
  411. view->getAttr(viewerPosName.toUtf8().constData(), vp);
  412. auto viewerAnglesName = QString("ViewerAngles%1").arg(useOldViewFormat ? "" : QString::number(i));
  413. view->getAttr(viewerAnglesName.toUtf8().constData(), va);
  414. }
  415. Matrix34 tm = Matrix34::CreateRotationXYZ(va);
  416. tm.SetTranslation(vp);
  417. auto viewportContextManager = AZ::Interface<AZ::RPI::ViewportContextRequestsInterface>::Get();
  418. if (auto viewportContext = viewportContextManager->GetViewportContextById(i))
  419. {
  420. viewportContext->SetCameraTransform(LYTransformToAZTransform(tm));
  421. }
  422. }
  423. }
  424. else
  425. {
  426. // Storing
  427. CLogFile::WriteLine("Storing View settings...");
  428. int numberOfGameViewports = GetIEditor()->GetViewManager()->GetNumberOfGameViewports();
  429. for (int i = 0; i < numberOfGameViewports; i++)
  430. {
  431. auto viewName = QString("View%1").arg(i);
  432. XmlNodeRef view = xmlAr.root->newChild(viewName.toUtf8().constData());
  433. CViewport* pVP = GetIEditor()->GetViewManager()->GetView(i);
  434. if (pVP)
  435. {
  436. Vec3 pos = pVP->GetViewTM().GetTranslation();
  437. Ang3 angles = Ang3::GetAnglesXYZ(Matrix33(pVP->GetViewTM()));
  438. auto viewerPosName = QString("ViewerPos%1").arg(i);
  439. view->setAttr(viewerPosName.toUtf8().constData(), pos);
  440. auto viewerAnglesName = QString("ViewerAngles%1").arg(i);
  441. view->setAttr(viewerAnglesName.toUtf8().constData(), angles);
  442. }
  443. }
  444. }
  445. }
  446. void CCryEditDoc::SerializeFogSettings(CXmlArchive& xmlAr)
  447. {
  448. if (xmlAr.bLoading)
  449. {
  450. CLogFile::WriteLine("Loading Fog settings...");
  451. XmlNodeRef fog = xmlAr.root->findChild("Fog");
  452. if (!fog)
  453. {
  454. return;
  455. }
  456. if (m_fogTemplate)
  457. {
  458. CXmlTemplate::GetValues(m_fogTemplate, fog);
  459. }
  460. }
  461. else
  462. {
  463. CLogFile::WriteLine("Storing Fog settings...");
  464. XmlNodeRef fog = xmlAr.root->newChild("Fog");
  465. if (m_fogTemplate)
  466. {
  467. CXmlTemplate::SetValues(m_fogTemplate, fog);
  468. }
  469. }
  470. }
  471. void CCryEditDoc::SetModifiedModules(EModifiedModule eModifiedModule, bool boSet)
  472. {
  473. if (!boSet)
  474. {
  475. m_modifiedModuleFlags &= ~eModifiedModule;
  476. }
  477. else
  478. {
  479. if (eModifiedModule == eModifiedNothing)
  480. {
  481. m_modifiedModuleFlags = eModifiedNothing;
  482. }
  483. else
  484. {
  485. m_modifiedModuleFlags |= eModifiedModule;
  486. }
  487. }
  488. }
  489. int CCryEditDoc::GetModifiedModule()
  490. {
  491. return m_modifiedModuleFlags;
  492. }
  493. bool CCryEditDoc::CanCloseFrame()
  494. {
  495. if (AzToolsFramework::ComponentModeFramework::InComponentMode())
  496. {
  497. AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequestBus::Broadcast(
  498. &AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequests::EndComponentMode);
  499. }
  500. // Ask the base class to ask for saving, which also includes the save
  501. // status of the plugins. Additionaly we query if all the plugins can exit
  502. // now. A reason for a failure might be that one of the plugins isn't
  503. // currently processing data or has other unsaved information which
  504. // are not serialized in the project file
  505. if (!SaveModified())
  506. {
  507. return false;
  508. }
  509. if (!GetIEditor()->GetPluginManager()->CanAllPluginsExitNow())
  510. {
  511. return false;
  512. }
  513. // If there is an export in process, exiting will corrupt it
  514. if (CGameExporter::GetCurrentExporter() != nullptr)
  515. {
  516. return false;
  517. }
  518. return true;
  519. }
  520. bool CCryEditDoc::SaveModified()
  521. {
  522. if (!IsModified())
  523. {
  524. return true;
  525. }
  526. bool usePrefabSystemForLevels = false;
  527. AzFramework::ApplicationRequests::Bus::BroadcastResult(
  528. usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
  529. if (!usePrefabSystemForLevels)
  530. {
  531. QMessageBox saveModifiedMessageBox(AzToolsFramework::GetActiveWindow());
  532. saveModifiedMessageBox.setText(QString("Save changes to %1?").arg(GetTitle()));
  533. saveModifiedMessageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
  534. saveModifiedMessageBox.setIcon(QMessageBox::Icon::Question);
  535. auto button = QMessageBox::question(
  536. AzToolsFramework::GetActiveWindow(), QString(), tr("Save changes to %1?").arg(GetTitle()),
  537. QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
  538. switch (button)
  539. {
  540. case QMessageBox::Cancel:
  541. return false;
  542. case QMessageBox::Yes:
  543. return DoFileSave();
  544. case QMessageBox::No:
  545. SetModifiedFlag(false);
  546. return true;
  547. }
  548. Q_UNREACHABLE();
  549. }
  550. else
  551. {
  552. AzToolsFramework::Prefab::TemplateId rootPrefabTemplateId = m_prefabEditorEntityOwnershipInterface->GetRootPrefabTemplateId();
  553. if (!m_prefabSystemComponentInterface->AreDirtyTemplatesPresent(rootPrefabTemplateId))
  554. {
  555. return true;
  556. }
  557. int prefabSaveSelection = m_prefabIntegrationInterface->HandleRootPrefabClosure(rootPrefabTemplateId);
  558. // In order to get the accept and reject codes of QDialog and QDialogButtonBox aligned, we do (1-prefabSaveSelection) here.
  559. // For example, QDialog::Rejected(0) is emitted when dialog is closed. But the int value corresponds to
  560. // QDialogButtonBox::AcceptRole(0).
  561. switch (1 - prefabSaveSelection)
  562. {
  563. case QDialogButtonBox::AcceptRole:
  564. return true;
  565. case QDialogButtonBox::RejectRole:
  566. return false;
  567. case QDialogButtonBox::InvalidRole:
  568. SetModifiedFlag(false);
  569. return true;
  570. }
  571. Q_UNREACHABLE();
  572. }
  573. }
  574. void CCryEditDoc::OnFileSaveAs()
  575. {
  576. CLevelFileDialog levelFileDialog(false);
  577. levelFileDialog.show();
  578. levelFileDialog.adjustSize();
  579. if (levelFileDialog.exec() == QDialog::Accepted)
  580. {
  581. if (OnSaveDocument(levelFileDialog.GetFileName()))
  582. {
  583. CCryEditApp::instance()->AddToRecentFileList(levelFileDialog.GetFileName());
  584. bool usePrefabSystemForLevels = false;
  585. AzFramework::ApplicationRequests::Bus::BroadcastResult(
  586. usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
  587. if (usePrefabSystemForLevels)
  588. {
  589. AzToolsFramework::Prefab::TemplateId rootPrefabTemplateId =
  590. m_prefabEditorEntityOwnershipInterface->GetRootPrefabTemplateId();
  591. SetModifiedFlag(m_prefabSystemComponentInterface->AreDirtyTemplatesPresent(rootPrefabTemplateId));
  592. }
  593. }
  594. }
  595. }
  596. bool CCryEditDoc::OnOpenDocument(const QString& lpszPathName)
  597. {
  598. TOpenDocContext context;
  599. if (!BeforeOpenDocument(lpszPathName, context))
  600. {
  601. return false;
  602. }
  603. return DoOpenDocument(context);
  604. }
  605. bool CCryEditDoc::BeforeOpenDocument(const QString& lpszPathName, TOpenDocContext& context)
  606. {
  607. const AZ::TimeMs timeMs = AZ::GetRealElapsedTimeMs();
  608. const double timeSec = AZ::TimeMsToSecondsDouble(timeMs);
  609. const CTimeValue loading_start_time(timeSec);
  610. bool usePrefabSystemForLevels = false;
  611. AzFramework::ApplicationRequests::Bus::BroadcastResult(
  612. usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
  613. if (!usePrefabSystemForLevels)
  614. {
  615. // ensure we close any open packs
  616. if (!GetIEditor()->GetLevelFolder().isEmpty())
  617. {
  618. GetIEditor()->GetSystem()->GetIPak()->ClosePack((GetIEditor()->GetLevelFolder() + "\\level.pak").toUtf8().data());
  619. }
  620. }
  621. // restore directory to root.
  622. QDir::setCurrent(GetIEditor()->GetPrimaryCDFolder());
  623. QString absolutePath = lpszPathName;
  624. QFileInfo fileInfo(absolutePath);
  625. QString friendlyDisplayName = Path::GetRelativePath(absolutePath, true);
  626. CLogFile::FormatLine("Opening level %s", friendlyDisplayName.toUtf8().data());
  627. // normalize the file path.
  628. absolutePath = Path::ToUnixPath(QFileInfo(absolutePath).canonicalFilePath());
  629. context.loading_start_time = loading_start_time;
  630. if (IsSliceFile(absolutePath))
  631. {
  632. context.absoluteLevelPath = Path::GamePathToFullPath(kLevelPathForSliceEditing);
  633. context.absoluteSlicePath = absolutePath;
  634. }
  635. else
  636. {
  637. context.absoluteLevelPath = absolutePath;
  638. context.absoluteSlicePath = "";
  639. }
  640. return true;
  641. }
  642. bool CCryEditDoc::DoOpenDocument(TOpenDocContext& context)
  643. {
  644. const CTimeValue& loading_start_time = context.loading_start_time;
  645. bool isPrefabEnabled = false;
  646. AzFramework::ApplicationRequests::Bus::BroadcastResult(isPrefabEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
  647. // normalize the path so that its the same in all following calls:
  648. QString levelFilePath = QFileInfo(context.absoluteLevelPath).absoluteFilePath();
  649. context.absoluteLevelPath = levelFilePath;
  650. m_bLoadFailed = false;
  651. auto pIPak = GetIEditor()->GetSystem()->GetIPak();
  652. QString levelFolderAbsolutePath = QFileInfo(context.absoluteLevelPath).absolutePath();
  653. if (!isPrefabEnabled)
  654. {
  655. // if the level pack exists, open that, too:
  656. QString levelPackFileAbsolutePath = QDir(levelFolderAbsolutePath).absoluteFilePath("level.pak");
  657. // we mount the pack (level.pak) using the folder its sitting in as the mountpoint (first parameter)
  658. pIPak->OpenPack(levelFolderAbsolutePath.toUtf8().constData(), levelPackFileAbsolutePath.toUtf8().constData());
  659. }
  660. TDocMultiArchive arrXmlAr = {};
  661. if (!isPrefabEnabled)
  662. {
  663. if (!LoadXmlArchiveArray(arrXmlAr, levelFilePath, levelFolderAbsolutePath))
  664. {
  665. m_bLoadFailed = true;
  666. return false;
  667. }
  668. }
  669. if (!LoadLevel(arrXmlAr, context.absoluteLevelPath))
  670. {
  671. m_bLoadFailed = true;
  672. }
  673. ReleaseXmlArchiveArray(arrXmlAr);
  674. if (m_bLoadFailed)
  675. {
  676. return false;
  677. }
  678. // Load AZ entities for the editor.
  679. if (context.absoluteSlicePath.isEmpty())
  680. {
  681. if (!LoadEntitiesFromLevel(context.absoluteLevelPath))
  682. {
  683. m_bLoadFailed = true;
  684. }
  685. }
  686. else
  687. {
  688. if (!LoadEntitiesFromSlice(context.absoluteSlicePath))
  689. {
  690. m_bLoadFailed = true;
  691. }
  692. }
  693. if (m_bLoadFailed)
  694. {
  695. return false;
  696. }
  697. StartStreamingLoad();
  698. const AZ::TimeMs timeMs = AZ::GetRealElapsedTimeMs();
  699. const double timeSec = AZ::TimeMsToSecondsDouble(timeMs);
  700. const CTimeValue loading_end_time(timeSec);
  701. CLogFile::FormatLine("-----------------------------------------------------------");
  702. CLogFile::FormatLine("Successfully opened document %s", context.absoluteLevelPath.toUtf8().data());
  703. CLogFile::FormatLine("Level loading time: %.2f seconds", (loading_end_time - loading_start_time).GetSeconds());
  704. CLogFile::FormatLine("-----------------------------------------------------------");
  705. // It assumes loaded levels have already been exported. Can be a big fat lie, though.
  706. // The right way would require us to save to the level folder the export status of the
  707. // level.
  708. SetLevelExported(true);
  709. return true;
  710. }
  711. bool CCryEditDoc::OnNewDocument()
  712. {
  713. DeleteContents();
  714. m_pathName.clear();
  715. m_slicePathName.clear();
  716. SetModifiedFlag(false);
  717. return true;
  718. }
  719. bool CCryEditDoc::OnSaveDocument(const QString& lpszPathName)
  720. {
  721. bool saveSuccess = false;
  722. bool shouldSaveLevel = true;
  723. if (gEnv->IsEditorSimulationMode())
  724. {
  725. // Don't allow saving in AI/Physics mode.
  726. // Prompt the user to exit Simulation Mode (aka AI/Phyics mode) before saving.
  727. QWidget* mainWindow = nullptr;
  728. AzToolsFramework::EditorRequests::Bus::BroadcastResult(mainWindow, &AzToolsFramework::EditorRequests::Bus::Events::GetMainWindow);
  729. QMessageBox msgBox(mainWindow);
  730. msgBox.setText(tr("You must exit AI/Physics mode before saving."));
  731. msgBox.setInformativeText(tr("The level will not be saved."));
  732. msgBox.setIcon(QMessageBox::Warning);
  733. msgBox.exec();
  734. }
  735. else
  736. {
  737. if (m_hasErrors || m_bLoadFailed)
  738. {
  739. QWidget* mainWindow = nullptr;
  740. AzToolsFramework::EditorRequests::Bus::BroadcastResult(
  741. mainWindow,
  742. &AzToolsFramework::EditorRequests::Bus::Events::GetMainWindow);
  743. // Prompt the user that saving may result in data loss. Most of the time this is not desired
  744. // (which is why 'cancel' is the default interaction), but this does provide users a way to still
  745. // save their level if this is the only way they can solve the erroneous data.
  746. QMessageBox msgBox(mainWindow);
  747. msgBox.setText(tr("Your level loaded with errors, you may lose work if you save."));
  748. msgBox.setInformativeText(tr("Do you want to save your changes?"));
  749. msgBox.setIcon(QMessageBox::Warning);
  750. msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Cancel);
  751. msgBox.setDefaultButton(QMessageBox::Cancel);
  752. int result = msgBox.exec();
  753. switch (result)
  754. {
  755. case QMessageBox::Save:
  756. // The user wishes to save, so don't bail.
  757. break;
  758. case QMessageBox::Cancel:
  759. // The user is canceling the save operation, so stop any saving from occuring.
  760. shouldSaveLevel = false;
  761. break;
  762. }
  763. }
  764. TSaveDocContext context;
  765. if (shouldSaveLevel && BeforeSaveDocument(lpszPathName, context))
  766. {
  767. DoSaveDocument(lpszPathName, context);
  768. saveSuccess = AfterSaveDocument(lpszPathName, context);
  769. }
  770. }
  771. return saveSuccess;
  772. }
  773. bool CCryEditDoc::BeforeSaveDocument(const QString& lpszPathName, TSaveDocContext& context)
  774. {
  775. // Don't save level data if any conflict exists
  776. if (HasLayerNameConflicts())
  777. {
  778. return false;
  779. }
  780. // Restore directory to root.
  781. QDir::setCurrent(GetIEditor()->GetPrimaryCDFolder());
  782. // If we do not have a level loaded, we will also have an empty path, and that will
  783. // cause problems later in the save process. Early out here if that's the case
  784. QString levelFriendlyName = QFileInfo(lpszPathName).fileName();
  785. if (levelFriendlyName.isEmpty())
  786. {
  787. return false;
  788. }
  789. CryLog("Saving to %s...", levelFriendlyName.toUtf8().data());
  790. GetIEditor()->Notify(eNotify_OnBeginSceneSave);
  791. bool bSaved(true);
  792. context.bSaved = bSaved;
  793. return true;
  794. }
  795. bool CCryEditDoc::HasLayerNameConflicts() const
  796. {
  797. AZStd::vector<AZ::Entity*> editorEntities;
  798. AzToolsFramework::EditorEntityContextRequestBus::Broadcast(
  799. &AzToolsFramework::EditorEntityContextRequestBus::Events::GetLooseEditorEntities,
  800. editorEntities);
  801. AZStd::unordered_map<AZStd::string, int> nameConflictMapping;
  802. for (AZ::Entity* entity : editorEntities)
  803. {
  804. AzToolsFramework::Layers::EditorLayerComponentRequestBus::Event(
  805. entity->GetId(),
  806. &AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::UpdateLayerNameConflictMapping,
  807. nameConflictMapping);
  808. }
  809. if (!nameConflictMapping.empty())
  810. {
  811. AzToolsFramework::Layers::NameConflictWarning* nameConflictWarning = new AzToolsFramework::Layers::NameConflictWarning(
  812. MainWindow::instance(),
  813. nameConflictMapping);
  814. nameConflictWarning->exec();
  815. return true;
  816. }
  817. return false;
  818. }
  819. bool CCryEditDoc::DoSaveDocument(const QString& filename, TSaveDocContext& context)
  820. {
  821. bool& bSaved = context.bSaved;
  822. if (!bSaved)
  823. {
  824. return false;
  825. }
  826. // Paranoia - we shouldn't get this far into the save routine without a level loaded (empty levelPath)
  827. // If nothing is loaded, we don't need to save anything
  828. if (filename.isEmpty())
  829. {
  830. bSaved = false;
  831. return false;
  832. }
  833. QString normalizedPath = Path::ToUnixPath(filename);
  834. if (IsSliceFile(normalizedPath))
  835. {
  836. bSaved = SaveSlice(normalizedPath);
  837. }
  838. else
  839. {
  840. bSaved = SaveLevel(normalizedPath);
  841. }
  842. // Changes filename for this document.
  843. SetPathName(normalizedPath);
  844. return bSaved;
  845. }
  846. bool CCryEditDoc::AfterSaveDocument([[maybe_unused]] const QString& lpszPathName, TSaveDocContext& context, bool bShowPrompt)
  847. {
  848. bool bSaved = context.bSaved;
  849. GetIEditor()->Notify(eNotify_OnEndSceneSave);
  850. if (!bSaved)
  851. {
  852. if (bShowPrompt)
  853. {
  854. QMessageBox::warning(QApplication::activeWindow(), QString(), QObject::tr("Save Failed"), QMessageBox::Ok);
  855. }
  856. CLogFile::WriteLine("$4Document saving has failed.");
  857. }
  858. else
  859. {
  860. CLogFile::WriteLine("$3Document successfully saved");
  861. SetModifiedFlag(false);
  862. SetModifiedModules(eModifiedNothing);
  863. }
  864. return bSaved;
  865. }
  866. static bool TryRenameFile(const QString& oldPath, const QString& newPath, int retryAttempts=10)
  867. {
  868. QFile(newPath).setPermissions(QFile::ReadOther | QFile::WriteOther);
  869. QFile::remove(newPath);
  870. // try a few times, something can lock the file (such as virus scanner, etc).
  871. for (int attempts = 0; attempts < retryAttempts; ++attempts)
  872. {
  873. if (QFile::rename(oldPath, newPath))
  874. {
  875. return true;
  876. }
  877. AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(100));
  878. }
  879. return false;
  880. }
  881. bool CCryEditDoc::SaveLevel(const QString& filename)
  882. {
  883. AZ_PROFILE_FUNCTION(Editor);
  884. QWaitCursor wait;
  885. CAutoCheckOutDialogEnableForAll enableForAll;
  886. QString fullPathName = Path::ToUnixPath(filename);
  887. QString originaLevelFilename = Path::GetFile(m_pathName);
  888. if (QFileInfo(filename).isRelative())
  889. {
  890. // Resolving the path through resolvepath would normalize and lowcase it, and in this case, we don't want that.
  891. fullPathName = Path::ToUnixPath(QDir(QString::fromUtf8(gEnv->pFileIO->GetAlias("@projectroot@"))).absoluteFilePath(fullPathName));
  892. }
  893. if (!CFileUtil::OverwriteFile(fullPathName))
  894. {
  895. return false;
  896. }
  897. {
  898. AZ_PROFILE_SCOPE(Editor, "CCryEditDoc::SaveLevel BackupBeforeSave");
  899. BackupBeforeSave();
  900. }
  901. // need to copy existing level data before saving to different folder
  902. const QString oldLevelFolder = Path::GetPath(GetLevelPathName()); // get just the folder name
  903. QString newLevelFolder = Path::GetPath(fullPathName);
  904. CFileUtil::CreateDirectory(newLevelFolder.toUtf8().data());
  905. GetIEditor()->GetGameEngine()->SetLevelPath(newLevelFolder);
  906. // QFileInfo operator== takes care of many side cases and will return true
  907. // if the folder is the same folder, even if other things (like slashes, etc) are wrong
  908. if (QFileInfo(oldLevelFolder) != QFileInfo(newLevelFolder))
  909. {
  910. // if we're saving to a new folder, we need to copy the old folder tree.
  911. auto pIPak = GetIEditor()->GetSystem()->GetIPak();
  912. const QString oldLevelPattern = QDir(oldLevelFolder).absoluteFilePath("*.*");
  913. const QString oldLevelName = Path::GetFile(GetLevelPathName());
  914. const QString oldLevelXml = Path::ReplaceExtension(oldLevelName, "xml");
  915. AZ::IO::ArchiveFileIterator findHandle = pIPak->FindFirst(oldLevelPattern.toUtf8().data(), AZ::IO::FileSearchLocation::Any);
  916. if (findHandle)
  917. {
  918. do
  919. {
  920. const QString sourceName{ QString::fromUtf8(findHandle.m_filename.data(), aznumeric_cast<int>(findHandle.m_filename.size())) };
  921. if ((findHandle.m_fileDesc.nAttrib & AZ::IO::FileDesc::Attribute::Subdirectory) == AZ::IO::FileDesc::Attribute::Subdirectory)
  922. {
  923. // we only end up here if sourceName is a folder name.
  924. bool skipDir = sourceName == "." || sourceName == "..";
  925. skipDir |= IsBackupOrTempLevelSubdirectory(sourceName);
  926. skipDir |= sourceName == "Layers"; // layers folder will be created and written out as part of saving
  927. if (!skipDir)
  928. {
  929. QString oldFolderName = QDir(oldLevelFolder).absoluteFilePath(sourceName);
  930. QString newFolderName = QDir(newLevelFolder).absoluteFilePath(sourceName);
  931. CFileUtil::CreateDirectory(newFolderName.toUtf8().data());
  932. CFileUtil::CopyTree(oldFolderName, newFolderName);
  933. }
  934. continue;
  935. }
  936. bool skipFile = sourceName.endsWith(".cry", Qt::CaseInsensitive) ||
  937. sourceName.endsWith(".ly", Qt::CaseInsensitive) ||
  938. sourceName == originaLevelFilename; // level file will be written out by saving, ignore the source one
  939. if (skipFile)
  940. {
  941. continue;
  942. }
  943. // close any paks in the source folder so that when the paks are re-opened there is
  944. // no stale cached metadata in the pak system
  945. if (sourceName.endsWith(".pak", Qt::CaseInsensitive))
  946. {
  947. QString oldPackName = QDir(oldLevelFolder).absoluteFilePath(sourceName);
  948. pIPak->ClosePack(oldPackName.toUtf8().constData());
  949. }
  950. QString destName = sourceName;
  951. // copy oldLevel.xml -> newLevel.xml
  952. if (sourceName.compare(oldLevelXml, Qt::CaseInsensitive) == 0)
  953. {
  954. destName = Path::ReplaceExtension(Path::GetFile(fullPathName), "xml");
  955. }
  956. QString oldFilePath = QDir(oldLevelFolder).absoluteFilePath(sourceName);
  957. QString newFilePath = QDir(newLevelFolder).absoluteFilePath(destName);
  958. CFileUtil::CopyFile(oldFilePath, newFilePath);
  959. } while ((findHandle = pIPak->FindNext(findHandle)));
  960. pIPak->FindClose(findHandle);
  961. }
  962. // ensure that copied files are not read-only
  963. CFileUtil::ForEach(newLevelFolder, [](const QString& filePath)
  964. {
  965. QFile(filePath).setPermissions(QFile::ReadOther | QFile::WriteOther);
  966. });
  967. }
  968. // Save level to XML archive.
  969. CXmlArchive xmlAr;
  970. Save(xmlAr);
  971. // temp files (to be ignored by AssetProcessor take the form $tmp[0-9]*_...). we will conform
  972. // to that to make this file invisible to AP until it has been written completely.
  973. QString tempSaveFile = QDir(newLevelFolder).absoluteFilePath("$tmp_levelSave.tmp");
  974. QFile(tempSaveFile).setPermissions(QFile::ReadOther | QFile::WriteOther);
  975. QFile::remove(tempSaveFile);
  976. // Save AZ entities to the editor level.
  977. bool contentsAllSaved = false; // abort level save if anything within it fails
  978. auto tempFilenameStrData = tempSaveFile.toStdString();
  979. auto filenameStrData = fullPathName.toStdString();
  980. bool isPrefabEnabled = false;
  981. AzFramework::ApplicationRequests::Bus::BroadcastResult(isPrefabEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
  982. if (!isPrefabEnabled)
  983. {
  984. AZStd::vector<char> entitySaveBuffer;
  985. bool savedEntities = false;
  986. CPakFile pakFile;
  987. {
  988. AZ_PROFILE_SCOPE(Editor, "CCryEditDoc::SaveLevel Open PakFile");
  989. if (!pakFile.Open(tempSaveFile.toUtf8().data(), false))
  990. {
  991. gEnv->pLog->LogWarning("Unable to open pack file %s for writing", tempSaveFile.toUtf8().data());
  992. return false;
  993. }
  994. }
  995. AZStd::vector<AZ::Entity*> editorEntities;
  996. AzToolsFramework::EditorEntityContextRequestBus::Broadcast(
  997. &AzToolsFramework::EditorEntityContextRequestBus::Events::GetLooseEditorEntities,
  998. editorEntities);
  999. AZStd::vector<AZ::Entity*> layerEntities;
  1000. AZ::SliceComponent::SliceReferenceToInstancePtrs instancesInLayers;
  1001. for (AZ::Entity* entity : editorEntities)
  1002. {
  1003. AzToolsFramework::Layers::LayerResult layerSaveResult(AzToolsFramework::Layers::LayerResult::CreateSuccess());
  1004. AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
  1005. layerSaveResult,
  1006. entity->GetId(),
  1007. &AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::WriteLayerAndGetEntities,
  1008. newLevelFolder,
  1009. layerEntities,
  1010. instancesInLayers);
  1011. layerSaveResult.MessageResult();
  1012. }
  1013. AZ::IO::ByteContainerStream<AZStd::vector<char>> entitySaveStream(&entitySaveBuffer);
  1014. {
  1015. AZ_PROFILE_SCOPE(Editor, "CCryEditDoc::SaveLevel Save Entities To Stream");
  1016. AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult(
  1017. savedEntities,
  1018. &AzToolsFramework::EditorEntityContextRequestBus::Events::SaveToStreamForEditor,
  1019. entitySaveStream,
  1020. layerEntities,
  1021. instancesInLayers);
  1022. }
  1023. for (AZ::Entity* entity : editorEntities)
  1024. {
  1025. AzToolsFramework::Layers::EditorLayerComponentRequestBus::Event(
  1026. entity->GetId(), &AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::RestoreEditorData);
  1027. }
  1028. if (savedEntities)
  1029. {
  1030. AZ_PROFILE_SCOPE(AzToolsFramework, "CCryEditDoc::SaveLevel Updated PakFile levelEntities.editor_xml");
  1031. pakFile.UpdateFile("levelentities.editor_xml", entitySaveBuffer.begin(), static_cast<int>(entitySaveBuffer.size()));
  1032. // Save XML archive to pak file.
  1033. bool bSaved = xmlAr.SaveToPak(Path::GetPath(tempSaveFile), pakFile);
  1034. if (bSaved)
  1035. {
  1036. contentsAllSaved = true;
  1037. }
  1038. else
  1039. {
  1040. gEnv->pLog->LogWarning("Unable to write the level data to file %s", tempSaveFile.toUtf8().data());
  1041. }
  1042. }
  1043. else
  1044. {
  1045. gEnv->pLog->LogWarning("Unable to generate entity data for level save %s", tempSaveFile.toUtf8().data());
  1046. }
  1047. pakFile.Close();
  1048. }
  1049. else
  1050. {
  1051. if (m_prefabEditorEntityOwnershipInterface)
  1052. {
  1053. AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
  1054. AZ_Assert(fileIO, "No File IO implementation available");
  1055. AZ::IO::HandleType tempSaveFileHandle;
  1056. AZ::IO::Result openResult = fileIO->Open(tempFilenameStrData.c_str(), AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeBinary, tempSaveFileHandle);
  1057. contentsAllSaved = openResult;
  1058. if (openResult)
  1059. {
  1060. AZ::IO::FileIOStream stream(tempSaveFileHandle, AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeBinary, false);
  1061. contentsAllSaved = m_prefabEditorEntityOwnershipInterface->SaveToStream(stream, AZStd::string_view(filenameStrData.data(), filenameStrData.size()));
  1062. stream.Close();
  1063. }
  1064. }
  1065. }
  1066. if (!contentsAllSaved)
  1067. {
  1068. AZ_Error("Editor", false, "Error when writing level '%s' into tmpfile '%s'", filenameStrData.c_str(), tempFilenameStrData.c_str());
  1069. QFile::remove(tempSaveFile);
  1070. return false;
  1071. }
  1072. if (!TryRenameFile(tempSaveFile, fullPathName))
  1073. {
  1074. gEnv->pLog->LogWarning("Unable to move file %s to %s when saving", tempSaveFile.toUtf8().data(), fullPathName.toUtf8().data());
  1075. return false;
  1076. }
  1077. // Commit changes to the disk.
  1078. _flushall();
  1079. AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast(&AzToolsFramework::ToolsApplicationEvents::OnSaveLevel);
  1080. return true;
  1081. }
  1082. bool CCryEditDoc::SaveSlice(const QString& filename)
  1083. {
  1084. using namespace AzToolsFramework::SliceUtilities;
  1085. // Gather entities from live slice in memory
  1086. AZ::SliceComponent* liveSlice = nullptr;
  1087. AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::BroadcastResult(liveSlice,
  1088. &AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Events::GetEditorRootSlice);
  1089. if (!liveSlice)
  1090. {
  1091. gEnv->pLog->LogWarning("Slice data not found.");
  1092. return false;
  1093. }
  1094. AZStd::unordered_set<AZ::EntityId> liveEntityIds;
  1095. if (!liveSlice->GetEntityIds(liveEntityIds))
  1096. {
  1097. gEnv->pLog->LogWarning("Error getting entities from slice.");
  1098. return false;
  1099. }
  1100. // Prevent save when there are multiple root entities.
  1101. bool foundRootEntity = false;
  1102. for (AZ::EntityId entityId : liveEntityIds)
  1103. {
  1104. AZ::EntityId parentId;
  1105. AZ::TransformBus::EventResult(parentId, entityId, &AZ::TransformBus::Events::GetParentId);
  1106. if (!parentId.IsValid())
  1107. {
  1108. if (foundRootEntity)
  1109. {
  1110. gEnv->pLog->LogWarning("Cannot save a slice with multiple root entities.");
  1111. return false;
  1112. }
  1113. foundRootEntity = true;
  1114. }
  1115. }
  1116. // Find target slice asset, and check if it's the same asset we opened
  1117. AZ::Data::AssetId targetAssetId;
  1118. AZ::Data::AssetCatalogRequestBus::BroadcastResult(targetAssetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, filename.toUtf8().data(), azrtti_typeid<AZ::SliceAsset>(), false);
  1119. QString openedFilepath = Path::ToUnixPath(Path::GetRelativePath(m_slicePathName, true));
  1120. AZ::Data::AssetId openedAssetId;
  1121. AZ::Data::AssetCatalogRequestBus::BroadcastResult(openedAssetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, openedFilepath.toUtf8().data(), azrtti_typeid<AZ::SliceAsset>(), false);
  1122. if (!targetAssetId.IsValid() || openedAssetId != targetAssetId)
  1123. {
  1124. gEnv->pLog->LogWarning("Slice editor can only modify existing slices. 'New Slice' and 'Save As' are not currently supported.");
  1125. return false;
  1126. }
  1127. AZ::Data::Asset<AZ::SliceAsset> sliceAssetRef = AZ::Data::AssetManager::Instance().GetAsset<AZ::SliceAsset>(targetAssetId, AZ::Data::AssetLoadBehavior::Default);
  1128. sliceAssetRef.BlockUntilLoadComplete();
  1129. if (!sliceAssetRef)
  1130. {
  1131. gEnv->pLog->LogWarning("Error loading slice: %s", filename.toUtf8().data());
  1132. return false;
  1133. }
  1134. // Get entities from target slice asset.
  1135. AZ::SliceComponent* assetSlice = sliceAssetRef.Get()->GetComponent();
  1136. if (!assetSlice)
  1137. {
  1138. gEnv->pLog->LogWarning("Error reading slice: %s", filename.toUtf8().data());
  1139. return false;
  1140. }
  1141. AZStd::unordered_set<AZ::EntityId> assetEntityIds;
  1142. if (!assetSlice->GetEntityIds(assetEntityIds))
  1143. {
  1144. gEnv->pLog->LogWarning("Error getting entities from slice: %s", filename.toUtf8().data());
  1145. return false;
  1146. }
  1147. AZStd::unordered_set<AZ::EntityId> entityAdds;
  1148. AZStd::unordered_set<AZ::EntityId> entityUpdates;
  1149. AZStd::unordered_set<AZ::EntityId> entityRemovals = assetEntityIds;
  1150. for (AZ::EntityId liveEntityId : liveEntityIds)
  1151. {
  1152. entityRemovals.erase(liveEntityId);
  1153. if (assetEntityIds.find(liveEntityId) != assetEntityIds.end())
  1154. {
  1155. entityUpdates.insert(liveEntityId);
  1156. }
  1157. else
  1158. {
  1159. entityAdds.insert(liveEntityId);
  1160. }
  1161. }
  1162. // Make a transaction targeting the specified slice
  1163. SliceTransaction::TransactionPtr transaction = SliceTransaction::BeginSlicePush(sliceAssetRef);
  1164. if (!transaction)
  1165. {
  1166. gEnv->pLog->LogWarning("Unable to update slice: %s", filename.toUtf8().data());
  1167. return false;
  1168. }
  1169. // Tell the transaction about all adds/updates/removals
  1170. for (AZ::EntityId id : entityAdds)
  1171. {
  1172. SliceTransaction::Result result = transaction->AddEntity(id);
  1173. if (!result)
  1174. {
  1175. gEnv->pLog->LogWarning("Error adding entity with ID %s to slice: %s\n\n%s",
  1176. id.ToString().c_str(), filename.toUtf8().data(), result.GetError().c_str());
  1177. return false;
  1178. }
  1179. }
  1180. for (AZ::EntityId id : entityRemovals)
  1181. {
  1182. SliceTransaction::Result result = transaction->RemoveEntity(id);
  1183. if (!result)
  1184. {
  1185. gEnv->pLog->LogWarning("Error removing entity with ID %s from slice: %s\n\n%s",
  1186. id.ToString().c_str(), filename.toUtf8().data(), result.GetError().c_str());
  1187. return false;
  1188. }
  1189. }
  1190. for (AZ::EntityId id : entityUpdates)
  1191. {
  1192. SliceTransaction::Result result = transaction->UpdateEntity(id);
  1193. if (!result)
  1194. {
  1195. gEnv->pLog->LogWarning("Error updating entity with ID %s in slice: %s\n\n%s",
  1196. id.ToString().c_str(), filename.toUtf8().data(), result.GetError().c_str());
  1197. return false;
  1198. }
  1199. }
  1200. // Commit
  1201. SliceTransaction::Result commitResult = transaction->Commit(
  1202. targetAssetId,
  1203. SlicePreSaveCallbackForWorldEntities,
  1204. nullptr,
  1205. SliceTransaction::SliceCommitFlags::DisableUndoCapture);
  1206. if (!commitResult)
  1207. {
  1208. gEnv->pLog->LogWarning("Failed to to save slice \"%s\".\n\nError:\n%s",
  1209. filename.toUtf8().data(), commitResult.GetError().c_str());
  1210. return false;
  1211. }
  1212. return true;
  1213. }
  1214. bool CCryEditDoc::LoadEntitiesFromLevel(const QString& levelPakFile)
  1215. {
  1216. bool isPrefabEnabled = false;
  1217. AzFramework::ApplicationRequests::Bus::BroadcastResult(isPrefabEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
  1218. bool loadedSuccessfully = false;
  1219. if (!isPrefabEnabled)
  1220. {
  1221. auto pakSystem = GetIEditor()->GetSystem()->GetIPak();
  1222. bool pakOpened = pakSystem->OpenPack(levelPakFile.toUtf8().data());
  1223. if (pakOpened)
  1224. {
  1225. const QString entityFilename = Path::GetPath(levelPakFile) + "levelentities.editor_xml";
  1226. CCryFile entitiesFile;
  1227. if (entitiesFile.Open(entityFilename.toUtf8().data(), "rt"))
  1228. {
  1229. AZStd::vector<char> fileBuffer;
  1230. fileBuffer.resize(entitiesFile.GetLength());
  1231. if (!fileBuffer.empty())
  1232. {
  1233. if (fileBuffer.size() == entitiesFile.ReadRaw(fileBuffer.begin(), fileBuffer.size()))
  1234. {
  1235. AZ::IO::ByteContainerStream<AZStd::vector<char>> fileStream(&fileBuffer);
  1236. AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult(
  1237. loadedSuccessfully,
  1238. &AzToolsFramework::EditorEntityContextRequestBus::Events::LoadFromStreamWithLayers,
  1239. fileStream,
  1240. levelPakFile);
  1241. }
  1242. else
  1243. {
  1244. AZ_Error(
  1245. "Editor", false, "Failed to load level entities because the file \"%s\" could not be read.",
  1246. entityFilename.toUtf8().data());
  1247. }
  1248. }
  1249. else
  1250. {
  1251. AZ_Error(
  1252. "Editor", false, "Failed to load level entities because the file \"%s\" is empty.", entityFilename.toUtf8().data());
  1253. }
  1254. entitiesFile.Close();
  1255. }
  1256. else
  1257. {
  1258. AZ_Error(
  1259. "Editor", false, "Failed to load level entities because the file \"%s\" was not found.",
  1260. entityFilename.toUtf8().data());
  1261. }
  1262. pakSystem->ClosePack(levelPakFile.toUtf8().data());
  1263. }
  1264. }
  1265. else
  1266. {
  1267. AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
  1268. AZ_Assert(fileIO, "No File IO implementation available");
  1269. AZ::IO::HandleType fileHandle;
  1270. AZ::IO::Result openResult = fileIO->Open(levelPakFile.toUtf8().data(), AZ::IO::OpenMode::ModeRead | AZ::IO::OpenMode::ModeBinary, fileHandle);
  1271. if (openResult)
  1272. {
  1273. AZ::IO::FileIOStream stream(fileHandle, AZ::IO::OpenMode::ModeRead | AZ::IO::OpenMode::ModeBinary, false);
  1274. AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult(
  1275. loadedSuccessfully, &AzToolsFramework::EditorEntityContextRequests::LoadFromStreamWithLayers, stream, levelPakFile);
  1276. stream.Close();
  1277. }
  1278. }
  1279. return loadedSuccessfully;
  1280. }
  1281. bool CCryEditDoc::LoadEntitiesFromSlice(const QString& sliceFile)
  1282. {
  1283. bool sliceLoaded = false;
  1284. {
  1285. AZ::IO::FileIOStream sliceFileStream(sliceFile.toUtf8().data(), AZ::IO::OpenMode::ModeRead);
  1286. if (!sliceFileStream.IsOpen())
  1287. {
  1288. AZ_Error("Editor", false, "Failed to load entities because the file \"%s\" could not be read.", sliceFile.toUtf8().data());
  1289. return false;
  1290. }
  1291. AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult(sliceLoaded, &AzToolsFramework::EditorEntityContextRequestBus::Events::LoadFromStream, sliceFileStream);
  1292. }
  1293. if (!sliceLoaded)
  1294. {
  1295. AZ_Error("Editor", false, "Failed to load entities from slice file \"%s\"", sliceFile.toUtf8().data());
  1296. return false;
  1297. }
  1298. return true;
  1299. }
  1300. bool CCryEditDoc::LoadLevel(TDocMultiArchive& arrXmlAr, const QString& absoluteCryFilePath)
  1301. {
  1302. bool isPrefabEnabled = false;
  1303. AzFramework::ApplicationRequests::Bus::BroadcastResult(isPrefabEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
  1304. auto pIPak = GetIEditor()->GetSystem()->GetIPak();
  1305. QString folderPath = QFileInfo(absoluteCryFilePath).absolutePath();
  1306. OnStartLevelResourceList();
  1307. // Load next level resource list.
  1308. if (!isPrefabEnabled)
  1309. {
  1310. pIPak->GetResourceList(AZ::IO::IArchive::RFOM_NextLevel)->Load(Path::Make(folderPath, "resourcelist.txt").toUtf8().data());
  1311. }
  1312. GetIEditor()->Notify(eNotify_OnBeginLoad);
  1313. CrySystemEventBus::Broadcast(&CrySystemEventBus::Events::OnCryEditorBeginLoad);
  1314. //GetISystem()->GetISystemEventDispatcher()->OnSystemEvent( ESYSTEM_EVENT_LEVEL_LOAD_START,0,0 );
  1315. DeleteContents();
  1316. // Set level path directly *after* DeleteContents(), since that will unload the previous level and clear the level path.
  1317. GetIEditor()->GetGameEngine()->SetLevelPath(folderPath);
  1318. SetModifiedFlag(true); // dirty during de-serialize
  1319. SetModifiedModules(eModifiedAll);
  1320. Load(arrXmlAr, absoluteCryFilePath);
  1321. GetISystem()->GetISystemEventDispatcher()->OnSystemEvent(ESYSTEM_EVENT_LEVEL_LOAD_END, 0, 0);
  1322. // We don't need next level resource list anymore.
  1323. if (!isPrefabEnabled)
  1324. {
  1325. pIPak->GetResourceList(AZ::IO::IArchive::RFOM_NextLevel)->Clear();
  1326. }
  1327. SetModifiedFlag(false); // start off with unmodified
  1328. SetModifiedModules(eModifiedNothing);
  1329. SetDocumentReady(true);
  1330. GetIEditor()->Notify(eNotify_OnEndLoad);
  1331. CrySystemEventBus::Broadcast(&CrySystemEventBus::Events::OnCryEditorEndLoad);
  1332. GetIEditor()->SetStatusText("Ready");
  1333. return true;
  1334. }
  1335. void CCryEditDoc::Hold(const QString& holdName)
  1336. {
  1337. Hold(holdName, holdName);
  1338. }
  1339. void CCryEditDoc::Hold(const QString& holdName, const QString& relativeHoldPath)
  1340. {
  1341. if (!IsDocumentReady() || GetEditMode() == CCryEditDoc::DocumentEditingMode::SliceEdit)
  1342. {
  1343. return;
  1344. }
  1345. QString levelPath = GetIEditor()->GetGameEngine()->GetLevelPath();
  1346. char resolvedLevelPath[AZ_MAX_PATH_LEN] = { 0 };
  1347. AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(levelPath.toUtf8().data(), resolvedLevelPath, AZ_MAX_PATH_LEN);
  1348. QString holdPath = QString::fromUtf8(resolvedLevelPath) + "/" + relativeHoldPath + "/";
  1349. QString holdFilename = holdPath + holdName + GetIEditor()->GetGameEngine()->GetLevelExtension();
  1350. // never auto-backup while we're trying to hold.
  1351. bool oldBackup = gSettings.bBackupOnSave;
  1352. gSettings.bBackupOnSave = false;
  1353. SaveLevel(holdFilename);
  1354. gSettings.bBackupOnSave = oldBackup;
  1355. GetIEditor()->GetGameEngine()->SetLevelPath(levelPath);
  1356. }
  1357. void CCryEditDoc::Fetch(const QString& relativeHoldPath, bool bShowMessages, bool bDelHoldFolder)
  1358. {
  1359. Fetch(relativeHoldPath, relativeHoldPath, bShowMessages, bDelHoldFolder ? FetchPolicy::DELETE_FOLDER : FetchPolicy::PRESERVE);
  1360. }
  1361. void CCryEditDoc::Fetch(const QString& holdName, const QString& relativeHoldPath, bool bShowMessages, FetchPolicy policy)
  1362. {
  1363. if (!IsDocumentReady() || GetEditMode() == CCryEditDoc::DocumentEditingMode::SliceEdit)
  1364. {
  1365. return;
  1366. }
  1367. QString levelPath = GetIEditor()->GetGameEngine()->GetLevelPath();
  1368. char resolvedLevelPath[AZ_MAX_PATH_LEN] = { 0 };
  1369. AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(levelPath.toUtf8().data(), resolvedLevelPath, AZ_MAX_PATH_LEN);
  1370. QString holdPath = QString::fromUtf8(resolvedLevelPath) + "/" + relativeHoldPath + "/";
  1371. QString holdFilename = holdPath + holdName + GetIEditor()->GetGameEngine()->GetLevelExtension();
  1372. {
  1373. QFile cFile(holdFilename);
  1374. // Open the file for writing, create it if needed
  1375. if (!cFile.open(QFile::ReadOnly))
  1376. {
  1377. if (bShowMessages)
  1378. {
  1379. QMessageBox::information(QApplication::activeWindow(), QString(), QObject::tr("You have to use 'Hold' before you can fetch!"));
  1380. }
  1381. return;
  1382. }
  1383. }
  1384. // Does the document contain unsaved data ?
  1385. if (bShowMessages && IsModified() &&
  1386. 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)
  1387. {
  1388. return;
  1389. }
  1390. GetIEditor()->FlushUndo();
  1391. TDocMultiArchive arrXmlAr = {};
  1392. if (!LoadXmlArchiveArray(arrXmlAr, holdFilename, holdPath))
  1393. {
  1394. 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);
  1395. AZ_Error("EditDoc", false, "Fetch failed to load the Xml Archive");
  1396. return;
  1397. }
  1398. // Load the state
  1399. LoadLevel(arrXmlAr, holdFilename);
  1400. // Load AZ entities for the editor.
  1401. LoadEntitiesFromLevel(holdFilename);
  1402. GetIEditor()->GetGameEngine()->SetLevelPath(levelPath);
  1403. GetIEditor()->FlushUndo();
  1404. switch (policy)
  1405. {
  1406. case FetchPolicy::DELETE_FOLDER:
  1407. CFileUtil::Deltree(holdPath.toUtf8().data(), true);
  1408. break;
  1409. case FetchPolicy::DELETE_LY_FILE:
  1410. CFileUtil::DeleteFile(holdFilename);
  1411. break;
  1412. default:
  1413. break;
  1414. }
  1415. }
  1416. //////////////////////////////////////////////////////////////////////////
  1417. namespace {
  1418. struct SFolderTime
  1419. {
  1420. QString folder;
  1421. time_t creationTime;
  1422. };
  1423. bool SortByCreationTime(SFolderTime& a, SFolderTime& b)
  1424. {
  1425. return a.creationTime < b.creationTime;
  1426. }
  1427. // This function, given a source folder to scan, returns all folders within that folder
  1428. // non-recursively. They will be sorted by time, with most recent first, and oldest last.
  1429. void CollectAllFoldersByTime(const char* sourceFolder, std::vector<SFolderTime>& outputFolders)
  1430. {
  1431. QString folderMask(sourceFolder);
  1432. AZ::IO::ArchiveFileIterator handle = gEnv->pCryPak->FindFirst((folderMask + "/*").toUtf8().data());
  1433. if (handle)
  1434. {
  1435. do
  1436. {
  1437. if (handle.m_filename.front() == '.')
  1438. {
  1439. continue;
  1440. }
  1441. if ((handle.m_fileDesc.nAttrib & AZ::IO::FileDesc::Attribute::Subdirectory) == AZ::IO::FileDesc::Attribute::Subdirectory)
  1442. {
  1443. SFolderTime ft;
  1444. ft.folder = QString::fromUtf8(handle.m_filename.data(), aznumeric_cast<int>(handle.m_filename.size()));
  1445. ft.creationTime = handle.m_fileDesc.tCreate;
  1446. outputFolders.push_back(ft);
  1447. }
  1448. } while (handle = gEnv->pCryPak->FindNext(handle));
  1449. gEnv->pCryPak->FindClose(handle);
  1450. }
  1451. std::sort(outputFolders.begin(), outputFolders.end(), SortByCreationTime);
  1452. }
  1453. }
  1454. bool CCryEditDoc::BackupBeforeSave(bool force)
  1455. {
  1456. // This function will copy the contents of an entire level folder to a backup folder
  1457. // and delete older ones based on user preferences.
  1458. if (!force && !gSettings.bBackupOnSave)
  1459. {
  1460. return true; // not an error
  1461. }
  1462. QString levelPath = GetIEditor()->GetGameEngine()->GetLevelPath();
  1463. if (levelPath.isEmpty())
  1464. {
  1465. return false;
  1466. }
  1467. char resolvedLevelPath[AZ_MAX_PATH_LEN] = { 0 };
  1468. AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(levelPath.toUtf8().data(), resolvedLevelPath, AZ_MAX_PATH_LEN);
  1469. QWaitCursor wait;
  1470. QString saveBackupPath = QString::fromUtf8(resolvedLevelPath) + "/" + kSaveBackupFolder;
  1471. std::vector<SFolderTime> folders;
  1472. CollectAllFoldersByTime(saveBackupPath.toUtf8().data(), folders);
  1473. for (int i = int(folders.size()) - gSettings.backupOnSaveMaxCount; i >= 0; --i)
  1474. {
  1475. CFileUtil::Deltree(QStringLiteral("%1/%2/").arg(saveBackupPath, folders[i].folder).toUtf8().data(), true);
  1476. }
  1477. QDateTime theTime = QDateTime::currentDateTime();
  1478. QString subFolder = theTime.toString("yyyy-MM-dd [HH.mm.ss]");
  1479. QString levelName = GetIEditor()->GetGameEngine()->GetLevelName();
  1480. QString backupPath = saveBackupPath + "/" + subFolder;
  1481. AZ::IO::FileIOBase::GetDirectInstance()->CreatePath(backupPath.toUtf8().data());
  1482. QString sourcePath = QString::fromUtf8(resolvedLevelPath) + "/";
  1483. QString ignoredFiles;
  1484. for (const char* backupOrTempFolderName : kBackupOrTempFolders)
  1485. {
  1486. if (!ignoredFiles.isEmpty())
  1487. {
  1488. ignoredFiles += "|";
  1489. }
  1490. ignoredFiles += QString::fromUtf8(backupOrTempFolderName);
  1491. }
  1492. // copy that whole tree:
  1493. AZ_TracePrintf("Editor", "Saving level backup to '%s'...\n", backupPath.toUtf8().data());
  1494. if (IFileUtil::ETREECOPYOK != CFileUtil::CopyTree(sourcePath, backupPath, true, false, ignoredFiles.toUtf8().data()))
  1495. {
  1496. gEnv->pLog->LogWarning("Attempting to save backup to %s before saving, but could not write all files.", backupPath.toUtf8().data());
  1497. return false;
  1498. }
  1499. return true;
  1500. }
  1501. void CCryEditDoc::SaveAutoBackup(bool bForce)
  1502. {
  1503. if (!bForce && (!gSettings.autoBackupEnabled || GetIEditor()->IsInGameMode()))
  1504. {
  1505. return;
  1506. }
  1507. QString levelPath = GetIEditor()->GetGameEngine()->GetLevelPath();
  1508. if (levelPath.isEmpty())
  1509. {
  1510. return;
  1511. }
  1512. static bool isInProgress = false;
  1513. if (isInProgress)
  1514. {
  1515. return;
  1516. }
  1517. isInProgress = true;
  1518. QWaitCursor wait;
  1519. QString autoBackupPath = levelPath + "/" + kAutoBackupFolder;
  1520. // collect all subfolders
  1521. std::vector<SFolderTime> folders;
  1522. CollectAllFoldersByTime(autoBackupPath.toUtf8().data(), folders);
  1523. for (int i = int(folders.size()) - gSettings.autoBackupMaxCount; i >= 0; --i)
  1524. {
  1525. CFileUtil::Deltree(QStringLiteral("%1/%2/").arg(autoBackupPath, folders[i].folder).toUtf8().data(), true);
  1526. }
  1527. // save new backup
  1528. QDateTime theTime = QDateTime::currentDateTime();
  1529. QString subFolder = theTime.toString(QStringLiteral("yyyy-MM-dd [HH.mm.ss]"));
  1530. QString levelName = GetIEditor()->GetGameEngine()->GetLevelName();
  1531. QString filename = autoBackupPath + "/" + subFolder + "/" + levelName + "/" + levelName + GetIEditor()->GetGameEngine()->GetLevelExtension();
  1532. SaveLevel(filename);
  1533. GetIEditor()->GetGameEngine()->SetLevelPath(levelPath);
  1534. isInProgress = false;
  1535. }
  1536. bool CCryEditDoc::IsLevelExported() const
  1537. {
  1538. return m_boLevelExported;
  1539. }
  1540. void CCryEditDoc::SetLevelExported(bool boExported)
  1541. {
  1542. m_boLevelExported = boExported;
  1543. }
  1544. void CCryEditDoc::RegisterListener(IDocListener* listener)
  1545. {
  1546. if (listener == nullptr)
  1547. {
  1548. return;
  1549. }
  1550. if (std::find(m_listeners.begin(), m_listeners.end(), listener) == m_listeners.end())
  1551. {
  1552. m_listeners.push_back(listener);
  1553. }
  1554. }
  1555. void CCryEditDoc::UnregisterListener(IDocListener* listener)
  1556. {
  1557. m_listeners.remove(listener);
  1558. }
  1559. void CCryEditDoc::LogLoadTime(int time) const
  1560. {
  1561. QString appFilePath = QDir::toNativeSeparators(QCoreApplication::applicationFilePath());
  1562. QString exePath = Path::GetPath(appFilePath);
  1563. QString filename = Path::Make(exePath, "LevelLoadTime.log");
  1564. QString level = GetIEditor()->GetGameEngine()->GetLevelPath();
  1565. CLogFile::FormatLine("[LevelLoadTime] Level %s loaded in %d seconds", level.toUtf8().data(), time / 1000);
  1566. #if defined(AZ_PLATFORM_WINDOWS)
  1567. SetFileAttributesW(filename.toStdWString().c_str(), FILE_ATTRIBUTE_ARCHIVE);
  1568. #endif
  1569. QFile file(filename);
  1570. if (!file.open(QFile::Append | QFile::Text))
  1571. {
  1572. return;
  1573. }
  1574. char version[50];
  1575. GetIEditor()->GetFileVersion().ToShortString(version, AZ_ARRAY_SIZE(version));
  1576. time = time / 1000;
  1577. QString text = QStringLiteral("\n[%1] Level %2 loaded in %3 seconds").arg(version, level).arg(time);
  1578. file.write(text.toUtf8());
  1579. }
  1580. void CCryEditDoc::SetDocumentReady(bool bReady)
  1581. {
  1582. m_bDocumentReady = bReady;
  1583. }
  1584. void CCryEditDoc::OnStartLevelResourceList()
  1585. {
  1586. // after loading another level we clear the RFOM_Level list, the first time the list should be empty
  1587. static bool bFirstTime = true;
  1588. if (bFirstTime)
  1589. {
  1590. const char* pResFilename = gEnv->pCryPak->GetResourceList(AZ::IO::IArchive::RFOM_Level)->GetFirst();
  1591. while (pResFilename)
  1592. {
  1593. // This should be fixed because ExecuteCommandLine is executed right after engine init as we assume the
  1594. // engine already has all data loaded an is initialized to process commands. Loading data afterwards means
  1595. // some init was done later which can cause problems when running in the engine batch mode (executing console commands).
  1596. gEnv->pLog->LogError("'%s' was loaded after engine init but before level load/new (should be fixed)", pResFilename);
  1597. pResFilename = gEnv->pCryPak->GetResourceList(AZ::IO::IArchive::RFOM_Level)->GetNext();
  1598. }
  1599. bFirstTime = false;
  1600. }
  1601. gEnv->pCryPak->GetResourceList(AZ::IO::IArchive::RFOM_Level)->Clear();
  1602. }
  1603. bool CCryEditDoc::DoFileSave()
  1604. {
  1605. if (GetEditMode() == CCryEditDoc::DocumentEditingMode::LevelEdit)
  1606. {
  1607. // If the file to save is the temporary level it should 'save as' since temporary levels will get deleted
  1608. const char* temporaryLevelName = GetTemporaryLevelName();
  1609. if (QString::compare(GetIEditor()->GetLevelName(), temporaryLevelName) == 0)
  1610. {
  1611. QString filename;
  1612. if (CCryEditApp::instance()->GetDocManager()->DoPromptFileName(filename, ID_FILE_SAVE_AS, 0, false, nullptr)
  1613. && !filename.isEmpty() && !QFileInfo(filename).exists())
  1614. {
  1615. if (SaveLevel(filename))
  1616. {
  1617. DeleteTemporaryLevel();
  1618. QString newLevelPath = filename.left(filename.lastIndexOf('/') + 1);
  1619. GetIEditor()->GetDocument()->SetPathName(filename);
  1620. GetIEditor()->GetGameEngine()->SetLevelPath(newLevelPath);
  1621. return true;
  1622. }
  1623. }
  1624. return false;
  1625. }
  1626. }
  1627. if (!IsDocumentReady())
  1628. {
  1629. return false;
  1630. }
  1631. return Internal::SaveLevel();
  1632. }
  1633. const char* CCryEditDoc::GetTemporaryLevelName() const
  1634. {
  1635. return gEnv->pConsole->GetCVar("g_TemporaryLevelName")->GetString();
  1636. }
  1637. void CCryEditDoc::DeleteTemporaryLevel()
  1638. {
  1639. QString tempLevelPath = (Path::GetEditingGameDataFolder() + "/Levels/" + GetTemporaryLevelName()).c_str();
  1640. GetIEditor()->GetSystem()->GetIPak()->ClosePacks(tempLevelPath.toUtf8().data());
  1641. CFileUtil::Deltree(tempLevelPath.toUtf8().data(), true);
  1642. }
  1643. void CCryEditDoc::InitEmptyLevel(int /*resolution*/, int /*unitSize*/, bool /*bUseTerrain*/)
  1644. {
  1645. GetIEditor()->SetStatusText("Initializing Level...");
  1646. OnStartLevelResourceList();
  1647. GetIEditor()->Notify(eNotify_OnBeginNewScene);
  1648. CLogFile::WriteLine("Preparing new document...");
  1649. //cleanup resources!
  1650. GetISystem()->GetISystemEventDispatcher()->OnSystemEvent(ESYSTEM_EVENT_LEVEL_POST_UNLOAD, 0, 0);
  1651. //////////////////////////////////////////////////////////////////////////
  1652. // Initialize defaults.
  1653. //////////////////////////////////////////////////////////////////////////
  1654. if (!GetIEditor()->IsInPreviewMode())
  1655. {
  1656. GetIEditor()->ReloadTemplates();
  1657. m_environmentTemplate = GetIEditor()->FindTemplate("Environment");
  1658. GetIEditor()->GetGameEngine()->SetLevelCreated(true);
  1659. GetIEditor()->GetGameEngine()->SetLevelCreated(false);
  1660. }
  1661. {
  1662. // Notify listeners.
  1663. std::list<IDocListener*> listeners = m_listeners;
  1664. for (IDocListener* listener : listeners)
  1665. {
  1666. listener->OnNewDocument();
  1667. }
  1668. }
  1669. // Tell the system that the level has been created/loaded.
  1670. GetISystem()->GetISystemEventDispatcher()->OnSystemEvent(ESYSTEM_EVENT_LEVEL_LOAD_END, 0, 0);
  1671. GetIEditor()->Notify(eNotify_OnEndNewScene);
  1672. SetModifiedFlag(false);
  1673. SetLevelExported(false);
  1674. SetModifiedModules(eModifiedNothing);
  1675. GetIEditor()->SetStatusText("Ready");
  1676. }
  1677. void CCryEditDoc::CreateDefaultLevelAssets([[maybe_unused]] int resolution, [[maybe_unused]] int unitSize)
  1678. {
  1679. AzToolsFramework::EditorLevelNotificationBus::Broadcast(&AzToolsFramework::EditorLevelNotificationBus::Events::OnNewLevelCreated);
  1680. }
  1681. void CCryEditDoc::OnEnvironmentPropertyChanged(IVariable* pVar)
  1682. {
  1683. if (pVar == nullptr)
  1684. {
  1685. return;
  1686. }
  1687. XmlNodeRef node = GetEnvironmentTemplate();
  1688. if (node == nullptr)
  1689. {
  1690. return;
  1691. }
  1692. // QVariant will not convert a void * to int, so do it manually.
  1693. int nKey = static_cast<int>(reinterpret_cast<intptr_t>(pVar->GetUserData().value<void*>()));
  1694. int nGroup = (nKey & 0xFFFF0000) >> 16;
  1695. int nChild = (nKey & 0x0000FFFF);
  1696. if (nGroup < 0 || nGroup >= node->getChildCount())
  1697. {
  1698. return;
  1699. }
  1700. XmlNodeRef groupNode = node->getChild(nGroup);
  1701. if (groupNode == nullptr)
  1702. {
  1703. return;
  1704. }
  1705. if (nChild < 0 || nChild >= groupNode->getChildCount())
  1706. {
  1707. return;
  1708. }
  1709. XmlNodeRef childNode = groupNode->getChild(nChild);
  1710. if (childNode == nullptr)
  1711. {
  1712. return;
  1713. }
  1714. QString childValue;
  1715. if (pVar->GetDataType() == IVariable::DT_COLOR)
  1716. {
  1717. Vec3 value;
  1718. pVar->Get(value);
  1719. QColor gammaColor = ColorLinearToGamma(ColorF(value.x, value.y, value.z));
  1720. childValue = QStringLiteral("%1,%2,%3").arg(gammaColor.red()).arg(gammaColor.green()).arg(gammaColor.blue());
  1721. }
  1722. else
  1723. {
  1724. pVar->Get(childValue);
  1725. }
  1726. childNode->setAttr("value", childValue.toUtf8().data());
  1727. }
  1728. QString CCryEditDoc::GetCryIndexPath(const char* levelFilePath) const
  1729. {
  1730. QString levelPath = Path::GetPath(levelFilePath);
  1731. QString levelName = Path::GetFileName(levelFilePath);
  1732. return Path::AddPathSlash(levelPath + levelName + "_editor");
  1733. }
  1734. bool CCryEditDoc::LoadXmlArchiveArray(TDocMultiArchive& arrXmlAr, const QString& absoluteLevelPath, const QString& levelPath)
  1735. {
  1736. auto pIPak = GetIEditor()->GetSystem()->GetIPak();
  1737. //if (m_pSWDoc->IsNull())
  1738. {
  1739. CXmlArchive* pXmlAr = new CXmlArchive();
  1740. if (!pXmlAr)
  1741. {
  1742. return false;
  1743. }
  1744. CXmlArchive& xmlAr = *pXmlAr;
  1745. xmlAr.bLoading = true;
  1746. // bound to the level folder, as if it were the assets folder.
  1747. // this mounts (whateverlevelname.ly) as @products@/Levels/whateverlevelname/ and thus it works...
  1748. bool openLevelPakFileSuccess = pIPak->OpenPack(levelPath.toUtf8().data(), absoluteLevelPath.toUtf8().data());
  1749. if (!openLevelPakFileSuccess)
  1750. {
  1751. return false;
  1752. }
  1753. CPakFile pakFile;
  1754. bool loadFromPakSuccess = xmlAr.LoadFromPak(levelPath, pakFile);
  1755. pIPak->ClosePack(absoluteLevelPath.toUtf8().data());
  1756. if (!loadFromPakSuccess)
  1757. {
  1758. return false;
  1759. }
  1760. FillXmlArArray(arrXmlAr, &xmlAr);
  1761. }
  1762. return true;
  1763. }
  1764. void CCryEditDoc::ReleaseXmlArchiveArray(TDocMultiArchive& arrXmlAr)
  1765. {
  1766. SAFE_DELETE(arrXmlAr[0]);
  1767. }
  1768. //////////////////////////////////////////////////////////////////////////
  1769. // AzToolsFramework::EditorEntityContextNotificationBus interface implementation
  1770. void CCryEditDoc::OnSliceInstantiated([[maybe_unused]] const AZ::Data::AssetId& sliceAssetId, [[maybe_unused]] AZ::SliceComponent::SliceInstanceAddress& sliceAddress, [[maybe_unused]] const AzFramework::SliceInstantiationTicket& ticket)
  1771. {
  1772. GetIEditor()->ResumeUndo();
  1773. }
  1774. void CCryEditDoc::OnSliceInstantiationFailed([[maybe_unused]] const AZ::Data::AssetId& sliceAssetId, [[maybe_unused]] const AzFramework::SliceInstantiationTicket& ticket)
  1775. {
  1776. GetIEditor()->ResumeUndo();
  1777. }
  1778. //////////////////////////////////////////////////////////////////////////
  1779. namespace AzToolsFramework
  1780. {
  1781. void CryEditDocFuncsHandler::Reflect(AZ::ReflectContext* context)
  1782. {
  1783. if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
  1784. {
  1785. // this will put these methods into the 'azlmbr.legacy.general' module
  1786. auto addLegacyGeneral = [](AZ::BehaviorContext::GlobalMethodBuilder methodBuilder)
  1787. {
  1788. methodBuilder->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
  1789. ->Attribute(AZ::Script::Attributes::Category, "Legacy/Editor")
  1790. ->Attribute(AZ::Script::Attributes::Module, "legacy.general");
  1791. };
  1792. addLegacyGeneral(behaviorContext->Method("save_level", ::Internal::SaveLevel, nullptr, "Saves the current level."));
  1793. }
  1794. }
  1795. }
  1796. #include <moc_CryEditDoc.cpp>