2
0

CryEditDoc.cpp 76 KB

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